]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add functions to look into the content of the cache
authorRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 18 Jan 2022 15:29:14 +0000 (16:29 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 22 Sep 2022 13:07:16 +0000 (15:07 +0200)
pdns/dnsdist-cache.cc
pdns/dnsdist-cache.hh
pdns/dnsparser.cc
pdns/dnsparser.hh
pdns/test-dnsdistpacketcache_cc.cc

index 7222a29162b0e47d897330802fcb62ef8c162673..b884df5002ea6ded0601ccf81ae27bf0e97a7cb2 100644 (file)
@@ -489,3 +489,110 @@ void DNSDistPacketCache::setSkippedOptions(const std::unordered_set<uint16_t>& o
 {
   d_optionsToSkip = optionsToSkip;
 }
+
+std::set<DNSName> DNSDistPacketCache::getDomainsContainingRecords(const ComboAddress& addr)
+{
+  std::set<DNSName> domains;
+
+  for (auto& shard : d_shards) {
+    auto map = shard.d_map.read_lock();
+
+    for (const auto& entry : *map) {
+      const CacheValue& value = entry.second;
+
+      try {
+        dnsheader dh;
+        if (value.len >= sizeof(dnsheader)) {
+          memcpy(&dh, value.value.data(), sizeof(dnsheader));
+        }
+        if (dh.rcode != RCode::NoError || (dh.ancount == 0 && dh.nscount == 0 && dh.arcount == 0)) {
+          continue;
+        }
+
+        bool found = false;
+        bool valid = visitDNSPacket(value.value, [addr, &found](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl, uint16_t rdatalength, const char* rdata) {
+
+          if (qtype == QType::A && qclass == QClass::IN && addr.isIPv4() && rdatalength == 4 && rdata != nullptr) {
+            ComboAddress parsed;
+            parsed.sin4.sin_family = AF_INET;
+            memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength);
+            if (parsed == addr) {
+              found = true;
+              return true;
+            }
+          }
+          else if (qtype == QType::AAAA && qclass == QClass::IN && addr.isIPv6() && rdatalength == 16 && rdata != nullptr) {
+            ComboAddress parsed;
+            parsed.sin6.sin6_family = AF_INET6;
+            memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength);
+            if (parsed == addr) {
+              found = true;
+              return true;
+            }
+          }
+
+          return false;
+        });
+
+        if (valid && found) {
+          domains.insert(value.qname);
+        }
+      }
+      catch (...) {
+        continue;
+      }
+    }
+  }
+
+  return domains;
+}
+
+std::set<ComboAddress> DNSDistPacketCache::getRecordsForDomain(const DNSName& domain)
+{
+  std::set<ComboAddress> addresses;
+
+  for (auto& shard : d_shards) {
+    auto map = shard.d_map.read_lock();
+
+    for (const auto& entry : *map) {
+      const CacheValue& value = entry.second;
+
+      try {
+        if (value.qname != domain) {
+          continue;
+        }
+
+        dnsheader dh;
+        if (value.len >= sizeof(dnsheader)) {
+          memcpy(&dh, value.value.data(), sizeof(dnsheader));
+        }
+        if (dh.rcode != RCode::NoError || (dh.ancount == 0 && dh.nscount == 0 && dh.arcount == 0)) {
+          continue;
+        }
+
+        visitDNSPacket(value.value, [&addresses](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl, uint16_t rdatalength, const char* rdata) {
+
+          if (qtype == QType::A && qclass == QClass::IN && rdatalength == 4 && rdata != nullptr) {
+            ComboAddress parsed;
+            parsed.sin4.sin_family = AF_INET;
+            memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength);
+            addresses.insert(parsed);
+          }
+          else if (qtype == QType::AAAA && qclass == QClass::IN && rdatalength == 16 && rdata != nullptr) {
+            ComboAddress parsed;
+            parsed.sin6.sin6_family = AF_INET6;
+            memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength);
+            addresses.insert(parsed);
+          }
+
+          return false;
+        });
+      }
+      catch (...) {
+        continue;
+      }
+    }
+  }
+
+  return addresses;
+}
index 23b321375c765faae21ecb1caf366f2aea38c40e..2ef6b7a8724886daebaf8d0aefb3efcb6f025a92 100644 (file)
@@ -56,6 +56,12 @@ public:
   uint64_t getCleanupCount() const { return d_cleanupCount; }
   uint64_t getEntriesCount();
   uint64_t dump(int fd);
+
+  /* get the list of domains (qnames) that contains the given address in an A or AAAA record */
+  std::set<DNSName> getDomainsContainingRecords(const ComboAddress& addr);
+  /* get the list of IP addresses contained in A or AAAA for a given domains (qname) */
+  std::set<ComboAddress> getRecordsForDomain(const DNSName& domain);
+
   void setSkippedOptions(const std::unordered_set<uint16_t>& optionsToSkip);
 
   bool isECSParsingEnabled() const { return d_parseECS; }
index 2e21461208370b6a77c5cdf63f82cf8933c3cf75..20ba535e07ffdb18497dcf899f272e5afcec6df2 100644 (file)
@@ -1129,3 +1129,54 @@ bool getEDNSUDPPayloadSizeAndZ(const char* packet, size_t length, uint16_t* payl
 
   return false;
 }
+
+bool visitDNSPacket(const std::string_view& packet, const std::function<bool(uint8_t, uint16_t, uint16_t, uint32_t, uint16_t, const char*)>& visitor)
+{
+  if (packet.size() < sizeof(dnsheader)) {
+    return false;
+  }
+
+  try
+  {
+    dnsheader dh;
+    memcpy(&dh, reinterpret_cast<const dnsheader*>(packet.data()), sizeof(dh));
+    uint64_t numrecords = ntohs(dh.ancount) + ntohs(dh.nscount) + ntohs(dh.arcount);
+    PacketReader reader(packet);
+
+    uint64_t n;
+    for (n = 0; n < ntohs(dh.qdcount) ; ++n) {
+      (void) reader.getName();
+      /* type and class */
+      reader.skip(4);
+    }
+
+    for (n = 0; n < numrecords; ++n) {
+      (void) reader.getName();
+
+      uint8_t section = n < ntohs(dh.ancount) ? 1 : (n < (ntohs(dh.ancount) + ntohs(dh.nscount)) ? 2 : 3);
+      uint16_t dnstype = reader.get16BitInt();
+      uint16_t dnsclass = reader.get16BitInt();
+
+      if (dnstype == QType::OPT) {
+        // not getting near that one with a stick
+        break;
+      }
+
+      uint32_t dnsttl = reader.get32BitInt();
+      uint16_t contentLength = reader.get16BitInt();
+      uint16_t pos = reader.getPosition();
+
+      bool done = visitor(section, dnsclass, dnstype, dnsttl, contentLength, &packet.at(pos));
+      if (done) {
+        return true;
+      }
+
+      reader.skip(contentLength);
+    }
+  }
+  catch (...) {
+    return false;
+  }
+
+  return true;
+}
index 3636677720ee2f3492d965ffbfb5d34ceb22cd64..9d8e14a54a4c2473782d059a324aa07a475f4dad 100644 (file)
@@ -470,6 +470,9 @@ uint32_t getDNSPacketMinTTL(const char* packet, size_t length, bool* seenAuthSOA
 uint32_t getDNSPacketLength(const char* packet, size_t length);
 uint16_t getRecordsOfTypeCount(const char* packet, size_t length, uint8_t section, uint16_t type);
 bool getEDNSUDPPayloadSizeAndZ(const char* packet, size_t length, uint16_t* payloadSize, uint16_t* z);
+/* call the visitor for every records in the answer, authority and additional sections, passing the section, class, type, ttl, rdatalength and rdata
+   to the visitor. Stops whenever the visitor returns false or at the end of the packet */
+bool visitDNSPacket(const std::string_view& packet, const std::function<bool(uint8_t, uint16_t, uint16_t, uint32_t, uint16_t, const char*)>& visitor);
 
 template<typename T>
 std::shared_ptr<T> getRR(const DNSRecord& dr)
index 4be01c2fcf1a272e1edd21cbcb4f1ac8d24a41e9..02050ff8753a23e0584615bc7d9882bf295f3a66 100644 (file)
@@ -124,7 +124,6 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) {
   }
 }
 
-
 BOOST_AUTO_TEST_CASE(test_PacketCacheSharded) {
   const size_t maxEntries = 150000;
   const size_t numberOfShards = 10;
@@ -722,4 +721,247 @@ BOOST_AUTO_TEST_CASE(test_PCDNSSECCollision) {
 
 }
 
+BOOST_AUTO_TEST_CASE(test_PacketCacheInspection) {
+  const size_t maxEntries = 100;
+  DNSDistPacketCache PC(maxEntries, 86400, 1);
+  BOOST_CHECK_EQUAL(PC.getSize(), 0U);
+  struct timespec queryTime;
+  gettime(&queryTime);  // does not have to be accurate ("realTime") in tests
+
+  ComboAddress remote;
+  bool dnssecOK = false;
+
+  uint32_t key = 0;
+
+  /* insert powerdns.com A 192.0.2.1, 192.0.2.2 */
+  {
+    DNSName qname("powerdns.com");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    {
+      ComboAddress addr("192.0.2.1");
+      pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+      pwR.xfrCAWithoutPort(4, addr);
+      pwR.commit();
+    }
+    {
+      ComboAddress addr("192.0.2.2");
+      pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+      pwR.xfrCAWithoutPort(4, addr);
+      pwR.commit();
+    }
+
+    PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+    BOOST_CHECK_EQUAL(PC.getSize(), key);
+  }
+
+  /* insert powerdns1.com A 192.0.2.3, 192.0.2.4, AAAA 2001:db8::3, 2001:db8::4 */
+  {
+    DNSName qname("powerdns1.com");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    {
+      ComboAddress addr("192.0.2.3");
+      pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+      pwR.xfrCAWithoutPort(4, addr);
+      pwR.commit();
+    }
+    {
+      ComboAddress addr("192.0.2.4");
+      pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+      pwR.xfrCAWithoutPort(4, addr);
+      pwR.commit();
+    }
+    {
+      ComboAddress addr("2001:db8::3");
+      pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+      pwR.xfrCAWithoutPort(6, addr);
+      pwR.commit();
+    }
+    {
+      ComboAddress addr("2001:db8::4");
+      pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+      pwR.xfrCAWithoutPort(6, addr);
+      pwR.commit();
+    }
+
+    PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+    BOOST_CHECK_EQUAL(PC.getSize(), key);
+  }
+
+  /* insert powerdns2.com NODATA */
+  {
+    DNSName qname("powerdns2.com");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    pwR.commit();
+    pwR.startRecord(qname, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY);
+    pwR.commit();
+    pwR.addOpt(4096, 0, 0);
+    pwR.commit();
+
+    PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+    BOOST_CHECK_EQUAL(PC.getSize(), key);
+  }
+
+  /* insert powerdns3.com AAAA 2001:db8::4, 2001:db8::5 */
+  {
+    DNSName qname("powerdns3.com");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    {
+      ComboAddress addr("2001:db8::4");
+      pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+      pwR.xfrCAWithoutPort(6, addr);
+      pwR.commit();
+    }
+    {
+      ComboAddress addr("2001:db8::5");
+      pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+      pwR.xfrCAWithoutPort(6, addr);
+      pwR.commit();
+    }
+
+    PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+    BOOST_CHECK_EQUAL(PC.getSize(), key);
+  }
+
+  /* insert powerdns4.com A 192.0.2.1 */
+  {
+    DNSName qname("powerdns4.com");
+    PacketBuffer query;
+    GenericDNSPacketWriter<PacketBuffer> pwQ(query, qname, QType::A, QClass::IN, 0);
+    pwQ.getHeader()->rd = 1;
+
+    PacketBuffer response;
+    GenericDNSPacketWriter<PacketBuffer> pwR(response, qname, QType::A, QClass::IN, 0);
+    pwR.getHeader()->rd = 1;
+    pwR.getHeader()->ra = 1;
+    pwR.getHeader()->qr = 1;
+    pwR.getHeader()->id = pwQ.getHeader()->id;
+    {
+      ComboAddress addr("192.0.2.1");
+      pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+      pwR.xfrCAWithoutPort(4, addr);
+      pwR.commit();
+    }
+
+    PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none);
+    BOOST_CHECK_EQUAL(PC.getSize(), key);
+  }
+
+  {
+    auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.1"));
+    BOOST_CHECK_EQUAL(domains.size(), 2U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns.com")), 1U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns4.com")), 1U);
+  }
+  {
+    auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.2"));
+    BOOST_CHECK_EQUAL(domains.size(), 1U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns.com")), 1U);
+  }
+  {
+    auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.3"));
+    BOOST_CHECK_EQUAL(domains.size(), 1U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
+  }
+  {
+    auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.4"));
+    BOOST_CHECK_EQUAL(domains.size(), 1U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
+  }
+  {
+    auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.5"));
+    BOOST_CHECK_EQUAL(domains.size(), 0U);
+  }
+  {
+    auto domains = PC.getDomainsContainingRecords(ComboAddress("2001:db8::3"));
+    BOOST_CHECK_EQUAL(domains.size(), 1U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
+  }
+  {
+    auto domains = PC.getDomainsContainingRecords(ComboAddress("2001:db8::4"));
+    BOOST_CHECK_EQUAL(domains.size(), 2U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns3.com")), 1U);
+  }
+  {
+    auto domains = PC.getDomainsContainingRecords(ComboAddress("2001:db8::5"));
+    BOOST_CHECK_EQUAL(domains.size(), 1U);
+    BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns3.com")), 1U);
+  }
+
+  {
+    auto records = PC.getRecordsForDomain(DNSName("powerdns.com"));
+    BOOST_CHECK_EQUAL(records.size(), 2U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.1")), 1U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.2")), 1U);
+  }
+
+  {
+    auto records = PC.getRecordsForDomain(DNSName("powerdns1.com"));
+    BOOST_CHECK_EQUAL(records.size(), 4U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.3")), 1U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.4")), 1U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::3")), 1U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U);
+  }
+
+  {
+    auto records = PC.getRecordsForDomain(DNSName("powerdns2.com"));
+    BOOST_CHECK_EQUAL(records.size(), 0U);
+  }
+
+  {
+    auto records = PC.getRecordsForDomain(DNSName("powerdns3.com"));
+    BOOST_CHECK_EQUAL(records.size(), 2U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U);
+  }
+
+  {
+    auto records = PC.getRecordsForDomain(DNSName("powerdns4.com"));
+    BOOST_CHECK_EQUAL(records.size(), 1U);
+    BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.1")), 1U);
+  }
+
+  {
+    auto records = PC.getRecordsForDomain(DNSName("powerdns5.com"));
+    BOOST_CHECK_EQUAL(records.size(), 0U);
+  }
+}
+
 BOOST_AUTO_TEST_SUITE_END()