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:
else:
cls._toResponderQueue.put(response, True, timeout)
+ if rawQuery:
+ rawResponse = True
+
if rawResponse:
return doh3_query(
query,
post=post,
additional_headers=customHeaders,
raw_response=rawResponse,
+ raw_query=rawQuery,
)
try:
post=post,
additional_headers=customHeaders,
raw_response=rawResponse,
+ raw_query=rawQuery,
)
except doqclient.StreamResetError as e:
if passExceptions:
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,
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,
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:
create_protocol=HttpClient,
post=post,
additional_headers=additional_headers,
+ raw_query=raw_query,
)
)
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)
class BogusDnsClientProtocol(DnsClientProtocol):
- def pack(self, data):
+ @staticmethod
+ def pack(data):
# serialize query
data = bytes(data)
data = struct.pack("!H", len(data) * 2) + data
configuration: QuicConfiguration,
host: str,
port: int,
- query: dns.message,
+ data: bytes,
timeout: float,
create_protocol=DnsClientProtocol,
) -> None:
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)
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,
)
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,
)
(_, 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'})
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
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,
serverName=self._serverName,
connection=connection,
passExceptions=passExceptions,
+ rawQuery=rawQuery,
)
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'})
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
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,
serverName=self._serverName,
connection=connection,
passExceptions=passExceptions,
+ rawQuery=rawQuery,
)
]
_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