ids.d_protoBufData = std::make_unique<InternalQueryState::ProtoBufData>(*d_protoBufData);
}
ids.cs = cs;
+ /* in case we want to support XFR over DoH, or the stream ID becomes used for QUIC */
+ ids.d_streamID = d_streamID;
return ids;
}
std::unique_ptr<DOHUnitInterface> IncomingHTTP2Connection::getDOHUnit(uint32_t streamID)
{
- // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay): clang-tidy is getting confused by assert()
- assert(streamID <= std::numeric_limits<IncomingHTTP2Connection::StreamID>::max());
+ if (streamID > std::numeric_limits<IncomingHTTP2Connection::StreamID>::max()) {
+ throw std::runtime_error("Invalid stream ID while retrieving DoH unit");
+ }
+
// NOLINTNEXTLINE(*-narrowing-conversions): generic interface between DNS and DoH with different types
auto query = std::move(d_currentStreams.at(static_cast<IncomingHTTP2Connection::StreamID>(streamID)));
return std::make_unique<IncomingDoHCrossProtocolContext>(std::move(query), std::dynamic_pointer_cast<IncomingHTTP2Connection>(shared_from_this()), streamID);
IOState IncomingHTTP2Connection::sendResponse(const struct timeval& now, TCPResponse&& response)
{
- // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay): clang-tidy is getting confused by assert()
- assert(response.d_idstate.d_streamID != -1);
+ if (response.d_idstate.d_streamID == -1) {
+ throw std::runtime_error("Invalid DoH stream ID while sending response");
+ }
auto& context = d_currentStreams.at(response.d_idstate.d_streamID);
uint32_t statusCode = 200U;
return;
}
- // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay): clang-tidy is getting confused by assert()
- assert(response.d_idstate.d_streamID != -1);
+ if (response.d_idstate.d_streamID == -1) {
+ throw std::runtime_error("Invalid DoH stream ID while handling I/O error notification");
+ }
+
auto& context = d_currentStreams.at(response.d_idstate.d_streamID);
context.d_buffer = std::move(response.d_buffer);
sendResponse(response.d_idstate.d_streamID, context, 502, d_ci.cs->dohFrontend->d_customResponseHeaders);
timespec now{};
gettime(&now);
+ if ((dnsQuestion.ids.qtype == QType::AXFR || dnsQuestion.ids.qtype == QType::IXFR) && (dnsQuestion.getProtocol() == dnsdist::Protocol::DoH || dnsQuestion.getProtocol() == dnsdist::Protocol::DoQ || dnsQuestion.getProtocol() == dnsdist::Protocol::DoH3)) {
+ dnsQuestion.editHeader([](dnsheader& header) {
+ header.rcode = RCode::NotImp;
+ header.qr = true;
+ return true;
+ });
+ return processQueryAfterRules(dnsQuestion, holders, selectedBackend);
+ }
+
if (!applyRulesToQuery(holders, dnsQuestion, now)) {
return ProcessQueryResult::Drop;
}
(_, receivedResponse) = self.sendQUICQuery(query, response=None, useQueue=False)
self.assertEqual(receivedResponse, response)
+
+class QUICXFRTests(object):
+
+ def testXFR(self):
+ """
+ QUIC: XFR
+ """
+ name = 'xfr.doq.tests.powerdns.com.'
+ for xfrType in [dns.rdatatype.AXFR, dns.rdatatype.IXFR]:
+ query = dns.message.make_query(name, xfrType, 'IN')
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.NOTIMP)
+
+ (_, receivedResponse) = self.sendQUICQuery(query, response=None, useQueue=False)
+ self.assertEqual(receivedResponse, expectedResponse)
class TestDOHLimitsH2O(DOHLimits, DNSDistDOHTest):
_dohLibrary = 'h2o'
+
+class DOHXFR(object):
+ _serverName = 'tls.tests.dnsdist.org'
+ _caCert = 'ca.pem'
+ _dohServerPort = pickAvailablePort()
+ _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _maxTCPConnsPerClient = 3
+ _config_template = """
+ newServer{address="127.0.0.1:%d", tcpOnly=true}
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, {library='%s'})
+ """
+ _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey', '_dohLibrary']
+
+ def testXFR(self):
+ """
+ DoH XFR: Check that XFR requests over DoH are refused with NotImp
+ """
+ name = 'xfr.doh.tests.powerdns.com.'
+ for xfrType in [dns.rdatatype.AXFR, dns.rdatatype.IXFR]:
+ query = dns.message.make_query(name, xfrType, 'IN')
+ url = self.getDOHGetURL(self._dohBaseURL, query)
+
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.NOTIMP)
+
+ (_, receivedResponse) = self.sendDOHQuery(self._dohServerPort, self._serverName, self._dohBaseURL, query, caFile=self._caCert, useQueue=False)
+
+ self.assertEqual(receivedResponse, expectedResponse)
+
+class TestDOHXFRNGHTTP2(DOHXFR, DNSDistDOHTest):
+ _dohLibrary = 'nghttp2'
+
+class TestDOHXFRH2O(DOHXFR, DNSDistDOHTest):
+ _dohLibrary = 'h2o'
from dnsdisttests import DNSDistTest
from dnsdisttests import pickAvailablePort
-from quictests import QUICTests, QUICWithCacheTests, QUICACLTests, QUICGetLocalAddressOnAnyBindTests
+from quictests import QUICTests, QUICWithCacheTests, QUICACLTests, QUICGetLocalAddressOnAnyBindTests, QUICXFRTests
import doh3client
class TestDOH3(QUICTests, DNSDistTest):
def sendQUICQuery(self, query, response=None, useQueue=True, connection=None):
return self.sendDOH3Query(self._doqServerPort, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection)
+
+class TestDOH3XFR(QUICXFRTests, 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 getQUICConnection(self):
+ return self.getDOQConnection(self._doqServerPort, self._caCert)
+
+ def sendQUICQuery(self, query, response=None, useQueue=True, connection=None):
+ return self.sendDOH3Query(self._doqServerPort, self._dohBaseURL, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection)
from dnsdisttests import DNSDistTest
from dnsdisttests import pickAvailablePort
from doqclient import quic_bogus_query
-from quictests import QUICTests, QUICWithCacheTests, QUICACLTests, QUICGetLocalAddressOnAnyBindTests
+from quictests import QUICTests, QUICWithCacheTests, QUICACLTests, QUICGetLocalAddressOnAnyBindTests, QUICXFRTests
import doqclient
from doqclient import quic_query
def sendQUICQuery(self, query, response=None, useQueue=True, connection=None):
return self.sendDOQQuery(self._doqServerPort, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection)
+class TestDOQXFR(QUICXFRTests, DNSDistTest):
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _caCert = 'ca.pem'
+ _doqServerPort = pickAvailablePort()
+ _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
+
+ def getQUICConnection(self):
+ return self.getDOQConnection(self._doqServerPort, self._caCert)
+
+ def sendQUICQuery(self, query, response=None, useQueue=True, connection=None):
+ return self.sendDOQQuery(self._doqServerPort, query, response=response, caFile=self._caCert, useQueue=useQueue, serverName=self._serverName, connection=connection)
+
class TestDOQCertificateReloading(DNSDistTest):
_consoleKey = DNSDistTest.generateConsoleKey()
_consoleKeyB64 = base64.b64encode(_consoleKey).decode('ascii')