]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add "too large" and "too many headers" regression tests
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 19 Feb 2026 13:46:04 +0000 (14:46 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 2 Apr 2026 07:29:46 +0000 (09:29 +0200)
Signed-off-by: Remi Gacogne <remi.gacogne@powerdns.com>
regression-tests.dnsdist/dnsdisttests.py
regression-tests.dnsdist/doh3client.py
regression-tests.dnsdist/doqclient.py
regression-tests.dnsdist/quictests.py
regression-tests.dnsdist/test_DOH.py
regression-tests.dnsdist/test_DOH3.py
regression-tests.dnsdist/test_DOQ.py

index 80f69d77ae1cef03845e9b1e74d69abc8c14581b..a8b62a0a5288dc6f074a6121dcea0c9b1eae3a4e 100644 (file)
@@ -1437,7 +1437,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
 
         try:
             (message, _) = doqclient.quic_query(
-                query, cls._dnsDistListeningAddr, timeout, port, verify=caFile, server_hostname=serverName
+                query, cls._dnsDistListeningAddr, timeout, port, verify=caFile, server_hostname=serverName, rawQuery=rawQuery
             )
         except doqclient.StreamResetError as e:
             if passExceptions:
@@ -1483,6 +1483,9 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
             else:
                 cls._toResponderQueue.put(response, True, timeout)
 
+        if rawQuery:
+            rawResponse = True
+
         if rawResponse:
             return doh3_query(
                 query,
@@ -1495,6 +1498,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
                 post=post,
                 additional_headers=customHeaders,
                 raw_response=rawResponse,
+                raw_query=rawQuery,
             )
 
         try:
@@ -1509,6 +1513,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
                 post=post,
                 additional_headers=customHeaders,
                 raw_response=rawResponse,
+                raw_query=rawQuery,
             )
         except doqclient.StreamResetError as e:
             if passExceptions:
index f78d5117cb8e6dfe0068b2a506741e23c050d2c4..0f22a0aaa31c0cc838429f7c120c636eebcdb001 100644 (file)
@@ -181,11 +181,16 @@ async def async_h3_query(
     post: bool,
     create_protocol=HttpClient,
     additional_headers: Optional[Dict] = None,
+    raw_query = False,
 ) -> Union[Tuple[str, Dict[str, str]], Tuple[asyncio.TimeoutError, Dict[str, str]]]:
 
     url = baseurl
+    if not raw_query:
+        query = query.to_wire()
+    else:
+        post = True
     if not post:
-        url = "{}?dns={}".format(baseurl, base64.urlsafe_b64encode(query.to_wire()).decode("UTF8").rstrip("="))
+        url = "{}?dns={}".format(baseurl, base64.urlsafe_b64encode(query).decode("UTF8").rstrip("="))
     async with connect(
         host,
         port,
@@ -199,7 +204,7 @@ async def async_h3_query(
                 answer = await perform_http_request(
                     client=client,
                     url=url,
-                    data=query.to_wire() if post else None,
+                    data=query if post else None,
                     include=False,
                     output_dir=None,
                     additional_headers=additional_headers,
@@ -221,6 +226,7 @@ def doh3_query(
     post=False,
     additional_headers=None,
     raw_response=False,
+    raw_query=False,
 ):
     configuration = QuicConfiguration(alpn_protocols=H3_ALPN, is_client=True, server_name=server_hostname)
     if verify:
@@ -237,6 +243,7 @@ def doh3_query(
             create_protocol=HttpClient,
             post=post,
             additional_headers=additional_headers,
+            raw_query=raw_query,
         )
     )
 
index cb31c7de5bfb1c4ee208554f908674a10429d8db..8ee1ed5b5c7324862b47eee422659e83c1a7996b 100644 (file)
@@ -17,14 +17,14 @@ class DnsClientProtocol(QuicConnectionProtocol):
         super().__init__(*args, **kwargs)
         self._ack_waiter: Any = None
 
-    def pack(self, data):
+    @staticmethod
+    def pack(data):
         # serialize query
         data = bytes(data)
         data = struct.pack("!H", len(data)) + data
         return data
 
-    async def query(self, query: dns.message) -> None:
-        data = self.pack(query.to_wire())
+    async def query(self, data) -> None:
         # send query and wait for answer
         stream_id = self._quic.get_next_available_stream_id()
         self._quic.send_stream_data(stream_id, data, end_stream=True)
@@ -50,7 +50,8 @@ class DnsClientProtocol(QuicConnectionProtocol):
 
 
 class BogusDnsClientProtocol(DnsClientProtocol):
-    def pack(self, data):
+    @staticmethod
+    def pack(data):
         # serialize query
         data = bytes(data)
         data = struct.pack("!H", len(data) * 2) + data
@@ -61,7 +62,7 @@ async def async_quic_query(
     configuration: QuicConfiguration,
     host: str,
     port: int,
-    query: dns.message,
+    data: bytes,
     timeout: float,
     create_protocol=DnsClientProtocol,
 ) -> None:
@@ -76,7 +77,7 @@ async def async_quic_query(
         print("Sending DNS query")
         try:
             async with async_timeout.timeout(timeout):
-                answer = await client.query(query)
+                answer = await client.query(data)
                 return (answer, client._quic.tls._peer_certificate.serial_number)
         except asyncio.TimeoutError as e:
             return (e, None)
@@ -88,16 +89,17 @@ class StreamResetError(Exception):
         super().__init__(message)
 
 
-def quic_query(query, host="127.0.0.1", timeout=2, port=853, verify=None, server_hostname=None):
+def quic_query(query, host="127.0.0.1", timeout=2, port=853, verify=None, server_hostname=None, rawQuery=False):
     configuration = QuicConfiguration(alpn_protocols=["doq"], is_client=True, server_name=server_hostname)
     if verify:
         configuration.load_verify_locations(verify)
+    data = DnsClientProtocol.pack(query.to_wire()) if not rawQuery else query
     (result, serial) = asyncio.run(
         async_quic_query(
             configuration=configuration,
             host=host,
             port=port,
-            query=query,
+            data=data,
             timeout=timeout,
             create_protocol=DnsClientProtocol,
         )
@@ -109,16 +111,17 @@ def quic_query(query, host="127.0.0.1", timeout=2, port=853, verify=None, server
     return (result, serial)
 
 
-def quic_bogus_query(query, host="127.0.0.1", timeout=2, port=853, verify=None, server_hostname=None):
+def quic_bogus_query(query, host="127.0.0.1", timeout=2, port=853, verify=None, server_hostname=None, rawQuery=False):
     configuration = QuicConfiguration(alpn_protocols=["doq"], is_client=True, server_name=server_hostname)
     if verify:
         configuration.load_verify_locations(verify)
+    data = BogusDnsClientProtocol.pack(query.to_wire()) if not rawQuery else query
     (result, _) = asyncio.run(
         async_quic_query(
             configuration=configuration,
             host=host,
             port=port,
-            query=query,
+            data=data,
             timeout=timeout,
             create_protocol=BogusDnsClientProtocol,
         )
index da6b6a9055c2b412a5c29c8eef750e22eeb46766..168e7daa1bcc8dd3d9dff5db1f50734c914820e0 100644 (file)
@@ -191,3 +191,21 @@ class QUICXFRTests(object):
 
             (_, receivedResponse) = self.sendQUICQuery(query, response=None, useQueue=False)
             self.assertEqual(receivedResponse, expectedResponse)
+
+class QUICTooLargeTests(object):
+
+    def testTooLarge(self):
+        """
+        QUIC: Too large
+        """
+        name = 'too-large.doq.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+
+        raw = query.to_wire()
+        padding = b'A'* (65536 - len(raw))
+        raw = raw + padding
+
+        (_, receivedResponse) = self.sendQUICQuery(raw, response=None, useQueue=False, rawQuery=True)
+        # None over DoQ
+        if receivedResponse is not None:
+            self.assertEqual(receivedResponse, {b':status': b'400', b'content-length': b'24'})
index f3523362b586c3d5229b1edfe60331a0aeb26c90..cf413262f83bebf105f0ae79a6a510d4e1ea162a 100644 (file)
@@ -543,6 +543,32 @@ class DOHTests(object):
         except pycurl.error:
             pass
 
+    def testDOHTooManyHeaders(self):
+        """
+        DOH: Too many HTTP headers
+        """
+        name = 'too-many-headers.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)
+        customHeaders = []
+        for idx in range(257):
+            customHeaders.append(f"X-{idx}: {idx}")
+        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
index 20c097d7f3e312454c01926185464516064ffbb0..abd049df4e31fe291bd1ab8ad0cc3bd78449e964 100644 (file)
@@ -3,14 +3,14 @@ import dns
 
 from dnsdisttests import DNSDistTest
 from dnsdisttests import pickAvailablePort
-from quictests import QUICTests, QUICACLTests, QUICGetLocalAddressOnAnyBindTests, QUICXFRTests
+from quictests import QUICTests, QUICACLTests, QUICGetLocalAddressOnAnyBindTests, QUICXFRTests, QUICTooLargeTests
 
 
 class DOH3Common(object):
     def getQUICConnection(self):
         return self.getDOQConnection(self._doqServerPort, self._caCert)
 
-    def sendQUICQuery(self, query, response=None, useQueue=True, connection=None, passExceptions=False):
+    def sendQUICQuery(self, query, response=None, useQueue=True, connection=None, passExceptions=False, rawQuery=False):
         return self.sendDOH3Query(
             self._doqServerPort,
             self._dohBaseURL,
@@ -21,6 +21,7 @@ class DOH3Common(object):
             serverName=self._serverName,
             connection=connection,
             passExceptions=passExceptions,
+            rawQuery=rawQuery,
         )
 
 
@@ -498,3 +499,47 @@ class TestDOH3CustomResponse(DOH3Common, DNSDistTest):
         receivedQuery.id = expectedQuery.id
         self.assertEqual(expectedQuery, receivedQuery)
         self.assertEqual(receivedResponse, response)
+
+class TestDOH3TooLarge(DOH3Common, QUICTooLargeTests, DNSDistTest):
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _doqServerPort = pickAvailablePort()
+    _dohBaseURL = ("https://%s:%d/" % (_serverName, _doqServerPort))
+    _config_template = """
+    newServer{address="127.0.0.1:%d", tcpOnly=true}
+
+    addDOH3Local("127.0.0.1:%d", "%s", "%s")
+    """
+    _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+    _verboseMode = True
+
+class TestDOH3TooManyHeadersLarge(DOH3Common, DNSDistTest):
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _doqServerPort = pickAvailablePort()
+    _dohBaseURL = ("https://%s:%d/" % (_serverName, _doqServerPort))
+    _config_template = """
+    newServer{address="127.0.0.1:%d", tcpOnly=true}
+
+    addDOH3Local("127.0.0.1:%d", "%s", "%s")
+    """
+    _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+    _verboseMode = True
+
+    def testTooManyHeaders(self):
+        """
+        QUIC: Too many headers
+        """
+        name = 'too-many-headers.doq.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+
+        customHeaders = {}
+        for idx in range(257):
+            customHeaders[str(idx)] = str(idx)
+
+        (_, receivedResponse) = self.sendDOH3Query(self._doqServerPort, self._dohBaseURL, query, response=None, caFile=self._caCert, useQueue=False, timeout=1.0, customHeaders=customHeaders, rawResponse=True)
+        self.assertEqual(receivedResponse, {b':status': b'400', b'content-length': b'31'})
index 46439b5373dfedb4e2a33b4494749b0591d5253f..97eca1f96e9dcdfad8611ed1a54401d608a03895 100644 (file)
@@ -4,7 +4,7 @@ import dns
 
 from dnsdisttests import DNSDistTest
 from dnsdisttests import pickAvailablePort
-from quictests import QUICTests, QUICWithCacheTests, QUICACLTests, QUICGetLocalAddressOnAnyBindTests, QUICXFRTests
+from quictests import QUICTests, QUICWithCacheTests, QUICACLTests, QUICGetLocalAddressOnAnyBindTests, QUICXFRTests, QUICTooLargeTests
 import doqclient
 
 
@@ -44,7 +44,7 @@ class DOQCommon(object):
     def getQUICConnection(self):
         return self.getDOQConnection(self._doqServerPort, self._caCert)
 
-    def sendQUICQuery(self, query, response=None, useQueue=True, connection=None, passExceptions=False):
+    def sendQUICQuery(self, query, response=None, useQueue=True, connection=None, passExceptions=False, rawQuery=False):
         return self.sendDOQQuery(
             self._doqServerPort,
             query,
@@ -54,6 +54,7 @@ class DOQCommon(object):
             serverName=self._serverName,
             connection=connection,
             passExceptions=passExceptions,
+            rawQuery=rawQuery,
         )
 
 
@@ -275,3 +276,18 @@ class TestDOQGetLocalAddressOnAnyBind(DOQCommon, QUICGetLocalAddressOnAnyBindTes
     ]
     _acl = ["127.0.0.1/32", "::1/128"]
     _skipListeningOnCL = True
+
+class TestDOQTooLarge(DOQCommon, QUICTooLargeTests, DNSDistTest):
+    _serverKey = 'server.key'
+    _serverCert = 'server.chain'
+    _serverName = 'tls.tests.dnsdist.org'
+    _caCert = 'ca.pem'
+    _doqServerPort = pickAvailablePort()
+    _dohBaseURL = ("https://%s:%d/" % (_serverName, _doqServerPort))
+    _config_template = """
+    newServer{address="127.0.0.1:%d", tcpOnly=true}
+
+    addDOQLocal("127.0.0.1:%d", "%s", "%s")
+    """
+    _config_params = ['_testServerPort', '_doqServerPort','_serverCert', '_serverKey']
+    _verboseMode = True