zeroScope = false;
}
uint32_t cacheKey = dnsResponse.ids.cacheKey;
- if (dnsResponse.ids.protocol == dnsdist::Protocol::DoH && dnsResponse.ids.forwardedOverUDP) {
- cacheKey = dnsResponse.ids.cacheKeyUDP;
+ if (dnsResponse.ids.protocol == dnsdist::Protocol::DoH && !dnsResponse.ids.forwardedOverUDP) {
+ cacheKey = dnsResponse.ids.cacheKeyTCP;
+ // disable zeroScope in that case, as we only have the "no-ECS" cache key for UDP
+ zeroScope = false;
}
- else if (zeroScope) {
+ if (zeroScope) {
// if zeroScope, pass the pre-ECS hash-key and do not pass the subnet to the cache
cacheKey = dnsResponse.ids.cacheKeyNoECS;
}
-
dnsResponse.ids.packetCache->insert(cacheKey, zeroScope ? boost::none : dnsResponse.ids.subnet, dnsResponse.ids.cacheFlags, dnsResponse.ids.dnssecOK, dnsResponse.ids.qname, dnsResponse.ids.qtype, dnsResponse.ids.qclass, response, dnsResponse.ids.forwardedOverUDP, dnsResponse.getHeader()->rcode, dnsResponse.ids.tempFailureTTL);
const auto& chains = dnsdist::configuration::getCurrentRuntimeConfiguration().d_ruleChains;
std::shared_ptr<ServerPool> serverPool = getPool(dnsQuestion.ids.poolName);
dnsQuestion.ids.packetCache = serverPool->packetCache;
selectBackendForOutgoingQuery(dnsQuestion, serverPool, selectedBackend);
+ bool willBeForwardedOverUDP = !dnsQuestion.overTCP() || dnsQuestion.ids.protocol == dnsdist::Protocol::DoH;
+ if (selectedBackend && selectedBackend->isTCPOnly()) {
+ willBeForwardedOverUDP = false;
+ }
uint32_t allowExpired = selectedBackend ? 0 : dnsdist::configuration::getCurrentRuntimeConfiguration().d_staleCacheEntriesTTL;
// we need ECS parsing (parseECS) to be true so we can be sure that the initial incoming query did not have an existing
// ECS option, which would make it unsuitable for the zero-scope feature.
if (dnsQuestion.ids.packetCache && !dnsQuestion.ids.skipCache && (!selectedBackend || !selectedBackend->d_config.disableZeroScope) && dnsQuestion.ids.packetCache->isECSParsingEnabled()) {
- if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, &dnsQuestion.ids.cacheKeyNoECS, dnsQuestion.ids.subnet, dnsQuestion.ids.dnssecOK, !dnsQuestion.overTCP(), allowExpired, false, true, false)) {
+ if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, &dnsQuestion.ids.cacheKeyNoECS, dnsQuestion.ids.subnet, dnsQuestion.ids.dnssecOK, willBeForwardedOverUDP, allowExpired, false, true, false)) {
vinfolog("Packet cache hit for query for %s|%s from %s (%s, %d bytes)", dnsQuestion.ids.qname.toLogString(), QType(dnsQuestion.ids.qtype).toString(), dnsQuestion.ids.origRemote.toStringWithPort(), dnsQuestion.ids.protocol.toString(), dnsQuestion.getData().size());
}
if (dnsQuestion.ids.packetCache && !dnsQuestion.ids.skipCache) {
- bool forwardedOverUDP = !dnsQuestion.overTCP();
- if (selectedBackend && selectedBackend->isTCPOnly()) {
- forwardedOverUDP = false;
- }
-
- /* we do not record a miss for queries received over DoH and forwarded over TCP
+ /* First lookup, which takes into account how the protocol over which the query will be forwarded.
+ For DoH, this lookup is done with the protocol set to TCP but we will retry over UDP below,
+ therefore we do not record a miss for queries received over DoH and forwarded over TCP
yet, as we will do a second-lookup */
- if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, &dnsQuestion.ids.cacheKey, dnsQuestion.ids.subnet, dnsQuestion.ids.dnssecOK, forwardedOverUDP, allowExpired, false, true, dnsQuestion.ids.protocol != dnsdist::Protocol::DoH || forwardedOverUDP)) {
+ if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, dnsQuestion.ids.protocol == dnsdist::Protocol::DoH ? &dnsQuestion.ids.cacheKeyTCP : &dnsQuestion.ids.cacheKey, dnsQuestion.ids.subnet, dnsQuestion.ids.dnssecOK, dnsQuestion.ids.protocol != dnsdist::Protocol::DoH && willBeForwardedOverUDP, allowExpired, false, true, dnsQuestion.ids.protocol != dnsdist::Protocol::DoH || !willBeForwardedOverUDP)) {
dnsdist::PacketMangling::editDNSHeaderFromPacket(dnsQuestion.getMutableData(), [flags = dnsQuestion.ids.origFlags](dnsheader& header) {
restoreFlags(&header, flags);
++dnsQuestion.ids.cs->responses;
return ProcessQueryResult::SendAnswer;
}
- if (dnsQuestion.ids.protocol == dnsdist::Protocol::DoH && !forwardedOverUDP) {
- /* do a second-lookup for UDP responses, but we do not want TC=1 answers */
- if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, &dnsQuestion.ids.cacheKeyUDP, dnsQuestion.ids.subnet, dnsQuestion.ids.dnssecOK, true, allowExpired, false, false, true)) {
+ if (dnsQuestion.ids.protocol == dnsdist::Protocol::DoH && willBeForwardedOverUDP) {
+ /* do a second-lookup for responses received over UDP, but we do not want TC=1 answers */
+ /* we need to be careful to keep the existing cache-key (TCP) */
+ if (dnsQuestion.ids.packetCache->get(dnsQuestion, dnsQuestion.getHeader()->id, &dnsQuestion.ids.cacheKey, dnsQuestion.ids.subnet, dnsQuestion.ids.dnssecOK, true, allowExpired, false, false, true)) {
if (!prepareOutgoingResponse(*dnsQuestion.ids.cs, dnsQuestion, true)) {
return ProcessQueryResult::Drop;
}
class TestCachingScopeZero(DNSDistTest):
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _caCert = 'ca.pem'
+ _dohServerPort = pickAvailablePort()
+ _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
_config_template = """
-- Be careful to enable ECS parsing in the packet cache, otherwise scope zero is disabled
pc = newPacketCache(100, {maxTTL=86400, minTTL=1, temporaryFailureTTL=60, staleTTL=60, dontAge=false, numberOfShards=1, deferrableInsertLock=true, maxNegativeTTL=3600, parseECS=true})
-- to unset it using rules before the first cache lookup)
addAction(RDRule(), SetECSAction("192.0.2.1/32"))
addAction(RDRule(), SetNoRecurseAction())
+
+ -- test the DoH special case (query received over TCP, forwarded over UDP)
+ addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, {library='nghttp2'})
"""
+ _config_params = ['_testServerPort', '_dohServerPort', '_serverCert', '_serverKey']
def testScopeZero(self):
"""
self.checkMessageEDNSWithECS(expectedQuery2, receivedQuery)
self.checkMessageNoEDNS(receivedResponse, response)
+ def testScopeZeroIncomingDoH(self):
+ """
+ Cache: Test the scope-zero feature with a query received over DoH, backend returns a scope of zero
+ """
+ ttl = 600
+ name = 'scope-zero-incoming-doh.cache.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'AAAA', 'IN')
+ query.flags &= ~dns.flags.RD
+ ecso = clientsubnetoption.ClientSubnetOption('127.0.0.0', 24)
+ expectedQuery = dns.message.make_query(name, 'AAAA', 'IN', use_edns=True, options=[ecso], payload=4096)
+ expectedQuery.flags &= ~dns.flags.RD
+ ecsoResponse = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24, 0)
+ expectedResponse = dns.message.make_response(query)
+ scopedResponse = dns.message.make_response(query)
+ scopedResponse.use_edns(edns=True, payload=4096, options=[ecsoResponse])
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '::1')
+ scopedResponse.answer.append(rrset)
+ expectedResponse.answer.append(rrset)
+
+ (receivedQuery, receivedResponse) = self.sendDOHQueryWrapper(query, scopedResponse)
+ receivedQuery.id = expectedQuery.id
+ self.checkMessageEDNSWithECS(expectedQuery, receivedQuery)
+ self.checkMessageNoEDNS(receivedResponse, expectedResponse)
+
+ # next query should hit the cache
+ (receivedQuery, receivedResponse) = self.sendDOHQueryWrapper(query, response=None, useQueue=False)
+ self.checkMessageNoEDNS(receivedResponse, expectedResponse)
+
class TestCachingScopeZeroButNoSubnetcheck(DNSDistTest):
_config_template = """