]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Stop caching responses to XFR queries 12422/head
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 23 Jan 2023 14:56:24 +0000 (15:56 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 23 Jan 2023 14:56:24 +0000 (15:56 +0100)
pdns/dnsdist-cache.cc
pdns/dnsdistdist/docs/advanced/axfr.rst
pdns/dnsdistdist/docs/upgrade_guide.rst
pdns/test-dnsdistpacketcache_cc.cc
regression-tests.dnsdist/test_Caching.py

index 3fe10336e07fcfe05630cd0fbfa90a73190a5a04..9b345e25e8af539df5650c5a9608bc6c5229da40 100644 (file)
@@ -122,6 +122,9 @@ void DNSDistPacketCache::insert(uint32_t key, const boost::optional<Netmask>& su
   if (response.size() < sizeof(dnsheader)) {
     return;
   }
+  if (qtype == QType::AXFR || qtype == QType::IXFR) {
+    return;
+  }
 
   uint32_t minTTL;
 
@@ -194,6 +197,11 @@ void DNSDistPacketCache::insert(uint32_t key, const boost::optional<Netmask>& su
 
 bool DNSDistPacketCache::get(DNSQuestion& dq, uint16_t queryId, uint32_t* keyOut, boost::optional<Netmask>& subnet, bool dnssecOK, bool receivedOverUDP, uint32_t allowExpired, bool skipAging, bool truncatedOK, bool recordMiss)
 {
+  if (dq.ids.qtype == QType::AXFR || dq.ids.qtype == QType::IXFR) {
+    d_misses++;
+    return false;
+  }
+
   const auto& dnsQName = dq.ids.qname.getStorage();
   uint32_t key = getKey(dnsQName, dq.ids.qname.wirelength(), dq.getData(), receivedOverUDP);
 
index 0be88d31f573a3a1219ab9e0da23b000c03257f9..822e7fc587f6728b17c16cbc3825a02bec4d2c55 100644 (file)
@@ -43,6 +43,9 @@ to do that are described in :doc:`Passing the source address to the backend <pas
     -- this rule will route SOA, AXFR and IXFR queries to a specific pool of servers
     addAction(OrRule({QTypeRule(DNSQType.SOA), QTypeRule(DNSQType.AXFR), QTypeRule(DNSQType.IXFR)}), PoolAction("primary"))
 
+.. versionchanged:: 1.8.0
+  Since 1.8.0, dnsdist will no longer cache responses to AXFR and IXFR queries.
+
 In front of secondaries
 -----------------------
 
index 88c9630ca0ae071d0618ef50b5eba2ed62000343..02b4dc6c913bd44490b76bd1b08499c662aa1be5 100644 (file)
@@ -4,6 +4,8 @@ Upgrade Guide
 1.7.x to 1.8.0
 --------------
 
+Responses to AXFR and IXFR queries are no longer cached.
+
 Cache-hits are now counted as responses in our metrics.
 
 1.7.0 to 1.7.1
index 33f6afabf75fb0c95af6edc17d920cd19ad7e9f8..b615ae72756c0b10cb12db86c157c16270d245cc 100644 (file)
@@ -1039,4 +1039,46 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheInspection) {
   }
 }
 
+BOOST_AUTO_TEST_CASE(test_PacketCacheXFR) {
+  const size_t maxEntries = 150000;
+  DNSDistPacketCache PC(maxEntries, 86400, 1);
+  BOOST_CHECK_EQUAL(PC.getSize(), 0U);
+
+  const std::set<QType> xfrTypes = { QType::AXFR, QType::IXFR };
+  for (const auto& type : xfrTypes) {
+    bool dnssecOK = false;
+    const time_t now = time(nullptr);
+    InternalQueryState ids;
+    ids.qtype = type;
+    ids.qclass = QClass::IN;
+    ids.protocol = dnsdist::Protocol::DoUDP;
+    ids.qname = DNSName("powerdns.com.");
+
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, ids.qname, ids.qtype, ids.qclass, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, ids.qname, ids.qtype, ids.qclass, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    pwR.startRecord(ids.qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+    pwR.xfr32BitInt(0x01020304);
+    pwR.commit();
+
+    uint32_t key = 0;
+    boost::optional<Netmask> subnet;
+    DNSQuestion dq(ids, query);
+    bool found = PC.get(dq, 0, &key, subnet, dnssecOK, receivedOverUDP);
+    BOOST_CHECK_EQUAL(found, false);
+    BOOST_CHECK(!subnet);
+
+    PC.insert(key, subnet, *(getFlagsFromDNSHeader(dq.getHeader())), dnssecOK, ids.qname, ids.qtype, ids.qclass, response, receivedOverUDP, 0, boost::none);
+    found = PC.get(dq, pwR.getHeader()->id, &key, subnet, dnssecOK, receivedOverUDP, 0, true);
+    BOOST_CHECK_EQUAL(found, false);
+  }
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index dafa0c89abba7f0de28a2510e3dd9974d49400e2..4dd5f88f0cafab6657dfd7ebce7a9133e59526fb 100644 (file)
@@ -233,6 +233,80 @@ class TestCaching(DNSDistTest):
             value = self._responsesCounter[key]
             self.assertEqual(value, numberOfQueries)
 
+    def testAXFRResponse(self):
+        """
+        Cache: AXFR should not be cached
+
+        dnsdist should not cache responses to AXFR queries.
+        """
+        name = 'axfr.cache.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AXFR', 'IN')
+        response = dns.message.make_response(query)
+        soa = dns.rrset.from_text(name,
+                                  60,
+                                  dns.rdataclass.IN,
+                                  dns.rdatatype.SOA,
+                                  'ns.' + name + ' hostmaster.' + name + ' 1 3600 3600 3600 60')
+        response.answer.append(soa)
+        response.answer.append(dns.rrset.from_text(name,
+                                                   60,
+                                                   dns.rdataclass.IN,
+                                                   dns.rdatatype.A,
+                                                   '192.0.2.1'))
+        response.answer.append(soa)
+        numberOfQueries = 5
+
+        for _ in range(numberOfQueries):
+            for method in ("sendUDPQuery", "sendTCPQuery"):
+                sender = getattr(self, method)
+                (receivedQuery, receivedResponse) = sender(query, response)
+                self.assertTrue(receivedQuery)
+                self.assertTrue(receivedResponse)
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(receivedResponse, response)
+
+        for key in self._responsesCounter:
+            value = self._responsesCounter[key]
+            self.assertEqual(value, numberOfQueries)
+
+    def testIXFRResponse(self):
+        """
+        Cache: IXFR should not be cached
+
+        dnsdist should not cache responses to IXFR queries.
+        """
+        name = 'ixfr.cache.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'IXFR', 'IN')
+        response = dns.message.make_response(query)
+        soa = dns.rrset.from_text(name,
+                                  60,
+                                  dns.rdataclass.IN,
+                                  dns.rdatatype.SOA,
+                                  'ns.' + name + ' hostmaster.' + name + ' 1 3600 3600 3600 60')
+        response.answer.append(soa)
+        response.answer.append(dns.rrset.from_text(name,
+                                                   60,
+                                                   dns.rdataclass.IN,
+                                                   dns.rdatatype.A,
+                                                   '192.0.2.1'))
+        response.answer.append(soa)
+        numberOfQueries = 5
+
+        for _ in range(numberOfQueries):
+            for method in ("sendUDPQuery", "sendTCPQuery"):
+                sender = getattr(self, method)
+                (receivedQuery, receivedResponse) = sender(query, response)
+                self.assertTrue(receivedQuery)
+                self.assertTrue(receivedResponse)
+                receivedQuery.id = query.id
+                self.assertEqual(query, receivedQuery)
+                self.assertEqual(receivedResponse, response)
+
+        for key in self._responsesCounter:
+            value = self._responsesCounter[key]
+            self.assertEqual(value, numberOfQueries)
+
     def testCacheExpiration(self):
         """
         Cache: Cache expiration