]> git.ipfire.org Git - thirdparty/pdns.git/blobdiff - regression-tests.dnsdist/test_DOH.py
Merge pull request #13594 from rgacogne/ddist-http1
[thirdparty/pdns.git] / regression-tests.dnsdist / test_DOH.py
index 5ed6382085f6f3dbc54c7a970a5850a975cd37c3..7fc186d982d47eb2580f1262bea8c45d32e1b4a9 100644 (file)
@@ -1,5 +1,6 @@
 #!/usr/bin/env python
 
+import base64
 import dns
 import os
 import time
@@ -7,26 +8,22 @@ import unittest
 import clientsubnetoption
 
 from dnsdistdohtests import DNSDistDOHTest
+from dnsdisttests import pickAvailablePort
 
 import pycurl
 from io import BytesIO
 
-class TestDOH(DNSDistDOHTest):
-
+class DOHTests(object):
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _customResponseHeader1 = 'access-control-allow-origin: *'
     _customResponseHeader2 = 'user-agent: derp'
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
-    newServer{address="127.0.0.1:%s"}
-
-    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})
-    dohFE = getDOHFrontend(0)
-    dohFE:setResponsesMap({newDOHResponseMapEntry('^/coffee$', 418, 'C0FFEE', {['FoO']='bar'})})
+    newServer{address="127.0.0.1:%d"}
 
     addAction("drop.doh.tests.powerdns.com.", DropAction())
     addAction("refused.doh.tests.powerdns.com.", RCodeAction(DNSRCode.REFUSED))
@@ -36,6 +33,7 @@ class TestDOH(DNSDistDOHTest):
     addAction(HTTPPathRegexRule("^/PowerDNS-[0-9]"), SpoofAction("6.7.8.9"))
     addAction("http-status-action.doh.tests.powerdns.com.", HTTPStatusAction(200, "Plaintext answer", "text/plain"))
     addAction("http-status-action-redirect.doh.tests.powerdns.com.", HTTPStatusAction(307, "https://doh.powerdns.org"))
+    addAction("no-backend.doh.tests.powerdns.com.", PoolAction('this-pool-has-no-backend'))
 
     function dohHandler(dq)
       if dq:getHTTPScheme() == 'https' and dq:getHTTPHost() == '%s:%d' and dq:getHTTPPath() == '/' and dq:getHTTPQueryString() == '' then
@@ -55,8 +53,13 @@ class TestDOH(DNSDistDOHTest):
       return DNSAction.None
     end
     addAction("http-lua.doh.tests.powerdns.com.", LuaAction(dohHandler))
+
+    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'})
+    dohFE = getDOHFrontend(0)
+    dohFE:setResponsesMap({newDOHResponseMapEntry('^/coffee$', 418, 'C0FFEE', {['FoO']='bar'})})
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_serverName', '_dohServerPort']
+    _config_params = ['_testServerPort', '_serverName', '_dohServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
+    _verboseMode = True
 
     def testDOHSimple(self):
         """
@@ -234,9 +237,174 @@ class TestDOH(DNSDistDOHTest):
         (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, caFile=self._caCert, query=query, response=None, useQueue=False)
         self.assertEqual(receivedResponse, expectedResponse)
 
+    def testDOHWithoutQuery(self):
+        """
+        DOH: Empty GET query
+        """
+        name = 'empty-get.doh.tests.powerdns.com.'
+        url = self._dohBaseURL
+        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+        conn.setopt(pycurl.URL, url)
+        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        conn.setopt(pycurl.CAINFO, self._caCert)
+        data = conn.perform_rb()
+        rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        self.assertEqual(rcode, 400)
+
+    def testDOHZeroQDCount(self):
+        """
+        DOH: qdcount == 0
+        """
+        if self._dohLibrary == 'h2o':
+            raise unittest.SkipTest('h2o tries to parse the qname early, so this check will fail')
+        name = 'zero-qdcount.doh.tests.powerdns.com.'
+        query = dns.message.Message()
+        query.id = 0
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.NOTIMP)
+
+        (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, caFile=self._caCert, query=query, response=None, useQueue=False)
+        self.assertEqual(receivedResponse, expectedResponse)
+
+    def testDOHShortPath(self):
+        """
+        DOH: Short path in GET query
+        """
+        name = 'short-path-get.doh.tests.powerdns.com.'
+        url = self._dohBaseURL + '/AA'
+        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+        conn.setopt(pycurl.URL, url)
+        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        conn.setopt(pycurl.CAINFO, self._caCert)
+        data = conn.perform_rb()
+        rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        self.assertEqual(rcode, 404)
+
+    def testDOHQueryNoParameter(self):
+        """
+        DOH: No parameter GET query
+        """
+        name = 'no-parameter-get.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        wire = query.to_wire()
+        b64 = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+        url = self._dohBaseURL + '?not-dns=' + b64
+        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+        conn.setopt(pycurl.URL, url)
+        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        conn.setopt(pycurl.CAINFO, self._caCert)
+        data = conn.perform_rb()
+        rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        self.assertEqual(rcode, 400)
+
+    def testDOHQueryInvalidBase64(self):
+        """
+        DOH: Invalid Base64 GET query
+        """
+        name = 'invalid-b64-get.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        wire = query.to_wire()
+        url = self._dohBaseURL + '?dns=' + '_-~~~~-_'
+        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+        conn.setopt(pycurl.URL, url)
+        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        conn.setopt(pycurl.CAINFO, self._caCert)
+        data = conn.perform_rb()
+        rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        self.assertEqual(rcode, 400)
+
+    def testDOHInvalidDNSHeaders(self):
+        """
+        DOH: Invalid DNS headers
+        """
+        name = 'invalid-dns-headers.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        query.flags |= dns.flags.QR
+        wire = query.to_wire()
+        b64 = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+        url = self._dohBaseURL + '?dns=' + b64
+        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+        conn.setopt(pycurl.URL, url)
+        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        conn.setopt(pycurl.CAINFO, self._caCert)
+        data = conn.perform_rb()
+        rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        self.assertEqual(rcode, 400)
+
+    def testDOHQueryInvalidMethod(self):
+        """
+        DOH: Invalid method
+        """
+        if self._dohLibrary == 'h2o':
+            raise unittest.SkipTest('h2o does not check the HTTP method')
+        name = 'invalid-method.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        wire = query.to_wire()
+        b64 = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+        url = self._dohBaseURL + '?dns=' + b64
+        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2)
+        conn.setopt(pycurl.URL, url)
+        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        conn.setopt(pycurl.CAINFO, self._caCert)
+        conn.setopt(pycurl.CUSTOMREQUEST, 'PATCH')
+        data = conn.perform_rb()
+        rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        self.assertEqual(rcode, 400)
+
+    def testDOHQueryInvalidALPN(self):
+        """
+        DOH: Invalid ALPN
+        """
+        alpn = ['bogus-alpn']
+        conn = self.openTLSConnection(self._dohServerPort, self._serverName, self._caCert, alpn=alpn)
+        try:
+            conn.send('AAAA')
+            response = conn.recv(65535)
+            self.assertFalse(response)
+        except:
+            pass
+
+    def testDOHHTTP1(self):
+        """
+        DOH: HTTP/1.1
+        """
+        if self._dohLibrary == 'h2o':
+            raise unittest.SkipTest('h2o supports HTTP/1.1, this test is only relevant for nghttp2')
+        name = 'http11.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        wire = query.to_wire()
+        b64 = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+        url = self._dohBaseURL + '?dns=' + b64
+        conn = pycurl.Curl()
+        conn.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_1_1)
+        conn.setopt(pycurl.HTTPHEADER, ["Content-type: application/dns-message",
+                                         "Accept: application/dns-message"])
+        conn.setopt(pycurl.URL, url)
+        conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
+        conn.setopt(pycurl.SSL_VERIFYPEER, 1)
+        conn.setopt(pycurl.SSL_VERIFYHOST, 2)
+        conn.setopt(pycurl.CAINFO, self._caCert)
+        data = conn.perform_rb()
+        rcode = conn.getinfo(pycurl.RESPONSE_CODE)
+        self.assertEqual(rcode, 400)
+        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')
+
     def testDOHInvalid(self):
         """
-        DOH: Invalid query
+        DOH: Invalid DNS query
         """
         name = 'invalid.doh.tests.powerdns.com.'
         invalidQuery = dns.message.make_query(name, 'A', 'IN', use_edns=False)
@@ -267,13 +435,43 @@ class TestDOH(DNSDistDOHTest):
         self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
         self.assertEqual(response, receivedResponse)
 
-    def testDOHWithoutQuery(self):
+    def testDOHInvalidHeaderName(self):
         """
-        DOH: Empty GET query
+        DOH: Invalid HTTP header name query
         """
-        name = 'empty-get.doh.tests.powerdns.com.'
-        url = self._dohBaseURL
-        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2.0)
+        name = 'invalid-header-name.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        query.id = 0
+        expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096)
+        expectedQuery.id = 0
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        response.answer.append(rrset)
+        # this header is invalid, see rfc9113 section 8.2.1. Field Validity
+        customHeaders = ['{}: test']
+        try:
+            (receivedQuery, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, response=response, caFile=self._caCert, customHeaders=customHeaders)
+            self.assertFalse(receivedQuery)
+            self.assertFalse(receivedResponse)
+        except pycurl.error:
+            pass
+
+    def testDOHNoBackend(self):
+        """
+        DOH: No backend
+        """
+        if self._dohLibrary == 'h2o':
+            raise unittest.SkipTest('h2o does not check the HTTP method')
+        name = 'no-backend.doh.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        wire = query.to_wire()
+        b64 = base64.urlsafe_b64encode(wire).decode('UTF8').rstrip('=')
+        url = self._dohBaseURL + '?dns=' + b64
+        conn = self.openDOHConnection(self._dohServerPort, self._caCert, timeout=2)
         conn.setopt(pycurl.URL, url)
         conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (self._serverName, self._dohServerPort)])
         conn.setopt(pycurl.SSL_VERIFYPEER, 1)
@@ -281,7 +479,7 @@ class TestDOH(DNSDistDOHTest):
         conn.setopt(pycurl.CAINFO, self._caCert)
         data = conn.perform_rb()
         rcode = conn.getinfo(pycurl.RESPONSE_CODE)
-        self.assertEqual(rcode, 400)
+        self.assertEqual(rcode, 403)
 
     def testDOHEmptyPOST(self):
         """
@@ -521,22 +719,27 @@ class TestDOH(DNSDistDOHTest):
         self.assertIn('foo: bar', headers)
         self.assertNotIn(self._customResponseHeader2, headers)
 
-class TestDOHSubPaths(DNSDistDOHTest):
+class TestDoHNGHTTP2(DOHTests, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDoHH2O(DOHTests, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
 
+class DOHSubPathsTests(object):
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s"}
 
     addAction(AllRule(), SpoofAction("3.4.5.6"))
 
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/PowerDNS" }, {exactPathMatching=false})
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/PowerDNS" }, {exactPathMatching=false, library='%s'})
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testSubPath(self):
         """
@@ -572,27 +775,33 @@ class TestDOHSubPaths(DNSDistDOHTest):
         # this path is not in the URLs map and should lead to a 404
         (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL + "NotPowerDNS", query, caFile=self._caCert, useQueue=False, rawResponse=True)
         self.assertTrue(receivedResponse)
-        self.assertEqual(receivedResponse, b'not found')
+        self.assertIn(receivedResponse, [b'there is no endpoint configured for this path', b'not found'])
         self.assertEqual(self._rcode, 404)
 
         # this path is below one in the URLs map and exactPathMatching is false, so we should be good
         (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL + 'PowerDNS/something', caFile=self._caCert, query=query, response=None, useQueue=False)
         self.assertEqual(receivedResponse, expectedResponse)
 
-class TestDOHAddingECS(DNSDistDOHTest):
+class TestDoHSubPathsNGHTTP2(DOHSubPathsTests, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDoHSubPathsH2O(DOHSubPathsTests, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class DOHAddingECSTests(object):
 
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s", useClientSubnet=true}
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
     setECSOverride(true)
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testDOHSimple(self):
         """
@@ -674,19 +883,21 @@ class TestDOHAddingECS(DNSDistDOHTest):
         self.checkQueryEDNSWithECS(expectedQuery, receivedQuery)
         self.checkResponseEDNSWithECS(response, receivedResponse)
 
-class TestDOHOverHTTP(DNSDistDOHTest):
+class TestDoHAddingECSNGHTTP2(DOHAddingECSTests, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDoHAddingECSH2O(DOHAddingECSTests, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
 
-    _dohServerPort = 8480
+class DOHOverHTTP(object):
+    _dohServerPort = pickAvailablePort()
     _serverName = 'tls.tests.dnsdist.org'
     _dohBaseURL = ("http://%s:%d/dns-query" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s"}
-    addDOHLocal("127.0.0.1:%s")
+    addDOHLocal("127.0.0.1:%s", nil, nil, '/dns-query', {library='%s'})
     """
-    _config_params = ['_testServerPort', '_dohServerPort']
-    _checkConfigExpectedOutput = b"""No certificate provided for DoH endpoint 127.0.0.1:8480, running in DNS over HTTP mode instead of DNS over HTTPS
-Configuration 'configs/dnsdist_TestDOHOverHTTP.conf' OK!
-"""
+    _config_params = ['_testServerPort', '_dohServerPort', '_dohLibrary']
 
     def testDOHSimple(self):
         """
@@ -739,23 +950,35 @@ Configuration 'configs/dnsdist_TestDOHOverHTTP.conf' OK!
         self.assertEqual(response, receivedResponse)
         self.checkResponseNoEDNS(response, receivedResponse)
 
-class TestDOHWithCache(DNSDistDOHTest):
+class TestDOHOverHTTPNGHTTP2(DOHOverHTTP, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+    _checkConfigExpectedOutput = b"""No certificate provided for DoH endpoint 127.0.0.1:%d, running in DNS over HTTP mode instead of DNS over HTTPS
+Configuration 'configs/dnsdist_TestDOHOverHTTPNGHTTP2.conf' OK!
+""" % (DOHOverHTTP._dohServerPort)
+
+class TestDOHOverHTTPH2O(DOHOverHTTP, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+    _checkConfigExpectedOutput = b"""No certificate provided for DoH endpoint 127.0.0.1:%d, running in DNS over HTTP mode instead of DNS over HTTPS
+Configuration 'configs/dnsdist_TestDOHOverHTTPH2O.conf' OK!
+""" % (DOHOverHTTP._dohServerPort)
+
+class DOHWithCache(object):
 
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/dns-query" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s"}
 
-    addDOHLocal("127.0.0.1:%s", "%s", "%s")
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", '/dns-query', {library='%s'})
 
     pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
     getPool(""):setCache(pc)
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testDOHCacheLargeAnswer(self):
         """
@@ -948,20 +1171,27 @@ class TestDOHWithCache(DNSDistDOHTest):
         (_, receivedResponse) = self.sendUDPQuery(expectedQuery, response=None, useQueue=False)
         self.assertEqual(response, receivedResponse)
 
-class TestDOHWithoutCacheControl(DNSDistDOHTest):
+class TestDOHWithCacheNGHTTP2(DOHWithCache, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+    _verboseMode = True
+
+class TestDOHWithCacheH2O(DOHWithCache, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class DOHWithoutCacheControl(object):
 
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s"}
 
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {sendCacheControlHeaders=false})
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {sendCacheControlHeaders=false, library='%s'})
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testDOHSimple(self):
         """
@@ -989,20 +1219,25 @@ class TestDOHWithoutCacheControl(DNSDistDOHTest):
         self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
         self.assertEqual(response, receivedResponse)
 
-class TestDOHFFI(DNSDistDOHTest):
+class TestDOHWithoutCacheControlNGHTTP2(DOHWithoutCacheControl, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDOHWithoutCacheControlH2O(DOHWithoutCacheControl, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
 
+class DOHFFI(object):
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _customResponseHeader1 = 'access-control-allow-origin: *'
     _customResponseHeader2 = 'user-agent: derp'
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s"}
 
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true})
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp",["UPPERCASE"]="VaLuE"}, keepIncomingHeaders=true, library='%s'})
 
     local ffi = require("ffi")
 
@@ -1035,7 +1270,7 @@ class TestDOHFFI(DNSDistDOHTest):
     end
     addAction("http-lua-ffi.doh.tests.powerdns.com.", LuaFFIAction(dohHandler))
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_serverName', '_dohServerPort']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary', '_serverName', '_dohServerPort']
 
     def testHTTPLuaFFIResponse(self):
         """
@@ -1051,21 +1286,29 @@ class TestDOHFFI(DNSDistDOHTest):
         self.assertEqual(self._rcode, 200)
         self.assertTrue('content-type: text/plain' in self._response_headers.decode())
 
-class TestDOHForwardedFor(DNSDistDOHTest):
+class TestDOHFFINGHTTP2(DOHFFI, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
 
+class TestDOHFFIH2O(DOHFFI, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class DOHForwardedFor(object):
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s"}
 
     setACL('192.0.2.1/32')
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {trustForwardedForHeader=true})
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {trustForwardedForHeader=true, library='%s'})
+    -- Set a maximum number of TCP connections per client, to exercise
+    -- that code along with X-Forwarded-For support
+    setMaxTCPConnectionsPerClient(2)
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testDOHAllowedForwarded(self):
         """
@@ -1112,23 +1355,29 @@ class TestDOHForwardedFor(DNSDistDOHTest):
         (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'])
 
         self.assertEqual(self._rcode, 403)
-        self.assertEqual(receivedResponse, b'dns query not allowed because of ACL')
+        self.assertEqual(receivedResponse, b'DoH query not allowed because of ACL')
+
+class TestDOHForwardedForNGHTTP2(DOHForwardedFor, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDOHForwardedForH2O(DOHForwardedFor, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
 
-class TestDOHForwardedForNoTrusted(DNSDistDOHTest):
+class DOHForwardedForNoTrusted(object):
 
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s"}
 
     setACL('192.0.2.1/32')
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {earlyACLDrop=true, library='%s'})
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testDOHForwardedUntrusted(self):
         """
@@ -1147,32 +1396,42 @@ class TestDOHForwardedForNoTrusted(DNSDistDOHTest):
                                     '127.0.0.1')
         response.answer.append(rrset)
 
-        (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'])
+        dropped = False
+        try:
+            (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'])
+            self.assertEqual(self._rcode, 403)
+            self.assertEqual(receivedResponse, b'DoH query not allowed because of ACL')
+        except pycurl.error as e:
+            dropped = True
 
-        self.assertEqual(self._rcode, 403)
-        self.assertEqual(receivedResponse, b'dns query not allowed because of ACL')
+        self.assertTrue(dropped)
+
+class TestDOHForwardedForNoTrustedNGHTTP2(DOHForwardedForNoTrusted, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
 
-class TestDOHFrontendLimits(DNSDistDOHTest):
+class TestDOHForwardedForNoTrustedH2O(DOHForwardedForNoTrusted, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class DOHFrontendLimits(object):
 
     # this test suite uses a different responder port
     # because it uses a different health check configuration
-    _testServerPort = 5395
+    _testServerPort = pickAvailablePort()
     _answerUnexpected = True
 
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
-
     _skipListeningOnCL = True
     _maxTCPConnsPerDOHFrontend = 5
     _config_template = """
     newServer{address="127.0.0.1:%s"}
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { maxConcurrentTCPConnections=%d })
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { maxConcurrentTCPConnections=%d, library='%s' })
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerDOHFrontend']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerDOHFrontend', '_dohLibrary']
     _alternateListeningAddr = '127.0.0.1'
     _alternateListeningPort = _dohServerPort
 
@@ -1186,7 +1445,10 @@ class TestDOHFrontendLimits(DNSDistDOHTest):
 
         for idx in range(self._maxTCPConnsPerDOHFrontend + 1):
             try:
-                conns.append(self.openTLSConnection(self._dohServerPort, self._serverName, self._caCert))
+                alpn = []
+                if self._dohLibrary != 'h2o':
+                    alpn.append('h2')
+                conns.append(self.openTLSConnection(self._dohServerPort, self._serverName, self._caCert, alpn=alpn))
             except:
                 conns.append(None)
 
@@ -1219,12 +1481,18 @@ class TestDOHFrontendLimits(DNSDistDOHTest):
         self.assertEqual(count, self._maxTCPConnsPerDOHFrontend)
         self.assertEqual(failed, 1)
 
-class TestProtocols(DNSDistDOHTest):
+class TestDOHFrontendLimitsNGHTTP2(DOHFrontendLimits, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDOHFrontendLimitsH2O(DOHFrontendLimits, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class Protocols(object):
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _customResponseHeader1 = 'access-control-allow-origin: *'
     _customResponseHeader2 = 'user-agent: derp'
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
@@ -1238,9 +1506,9 @@ class TestProtocols(DNSDistDOHTest):
 
     addAction("protocols.doh.tests.powerdns.com.", LuaAction(checkDOH))
     newServer{address="127.0.0.1:%s"}
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testProtocolDOH(self):
         """
@@ -1260,23 +1528,29 @@ class TestProtocols(DNSDistDOHTest):
         self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
         self.assertEqual(response, receivedResponse)
 
-class TestDOHWithPCKS12Cert(DNSDistDOHTest):
+class TestProtocolsNGHTTP2(Protocols, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestProtocolsH2O(Protocols, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class DOHWithPKCS12Cert(object):
     _serverCert = 'server.p12'
     _pkcs12Password = 'passw0rd'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s"}
     cert=newTLSCertificate("%s", {password="%s"})
-    addDOHLocal("127.0.0.1:%s", cert, "", { "/" })
+    addDOHLocal("127.0.0.1:%s", cert, "", { "/" }, {library='%s'})
     """
-    _config_params = ['_testServerPort', '_serverCert', '_pkcs12Password', '_dohServerPort']
+    _config_params = ['_testServerPort', '_serverCert', '_pkcs12Password', '_dohServerPort', '_dohLibrary']
 
-    def testProtocolDOH(self):
+    def testPKCS12DOH(self):
         """
-        DoH: Test Simple DOH Query with a password protected PCKS12 file configured
+        DoH: Test Simple DOH Query with a password protected PKCS12 file configured
         """
         name = 'simple.doh.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
@@ -1297,18 +1571,24 @@ class TestDOHWithPCKS12Cert(DNSDistDOHTest):
         receivedQuery.id = expectedQuery.id
         self.assertEqual(expectedQuery, receivedQuery)
 
-class TestDOHForwardedToTCPOnly(DNSDistDOHTest):
+class TestDOHWithPKCS12CertNGHTTP2(DOHWithPKCS12Cert, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDOHWithPKCS12CertH2O(DOHWithPKCS12Cert, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class DOHForwardedToTCPOnly(object):
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s", tcpOnly=true}
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {library='%s'})
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
 
     def testDOHTCPOnly(self):
         """
@@ -1332,20 +1612,26 @@ class TestDOHForwardedToTCPOnly(DNSDistDOHTest):
         self.assertEqual(receivedQuery, query)
         self.assertEqual(receivedResponse, response)
 
-class TestDOHLimits(DNSDistDOHTest):
+class TestDOHForwardedToTCPOnlyNGHTTP2(DOHForwardedToTCPOnly, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDOHForwardedToTCPOnlyH2O(DOHForwardedToTCPOnly, DNSDistDOHTest):
+    _dohLibrary = 'h2o'
+
+class DOHLimits(object):
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
-    _dohServerPort = 8443
+    _dohServerPort = pickAvailablePort()
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _serverKey = 'server.key'
     _serverCert = 'server.chain'
     _maxTCPConnsPerClient = 3
     _config_template = """
-    newServer{address="127.0.0.1:%s"}
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
-    setMaxTCPConnectionsPerClient(%s)
+    newServer{address="127.0.0.1:%d"}
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, {library='%s'})
+    setMaxTCPConnectionsPerClient(%d)
     """
-    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_maxTCPConnsPerClient']
+    _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary', '_maxTCPConnsPerClient']
 
     def testConnsPerClient(self):
         """
@@ -1385,3 +1671,9 @@ class TestDOHLimits(DNSDistDOHTest):
 
         self.assertEqual(count, self._maxTCPConnsPerClient)
         self.assertEqual(failed, 1)
+
+class TestDOHLimitsNGHTTP2(DOHLimits, DNSDistDOHTest):
+    _dohLibrary = 'nghttp2'
+
+class TestDOHLimitsH2O(DOHLimits, DNSDistDOHTest):
+    _dohLibrary = 'h2o'