]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Speed up response content matching
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 25 Jul 2025 14:12:39 +0000 (16:12 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 25 Jul 2025 14:17:53 +0000 (16:17 +0200)
This commit introduces a new method to compare a `DNSName`
against a view of raw, wire-format bytes, skipping the
allocation and copy that is usually required to get a
second `DNSName` object to compare against.
This signifitcantly reduces the amount of time matching
a DNS response received from a backend against the content
we expect to find.

Signed-off-by: Remi Gacogne <remi.gacogne@powerdns.com>
pdns/dnsdistdist/dnsdist.cc
pdns/dnsname.cc
pdns/dnsname.hh
pdns/test-dnsname_cc.cc

index 35e489979853efca27a8862aee62ae4e9f55fe67..3b1e3d8cac15f11c8999ec858c43e38480de89aa 100644 (file)
@@ -285,12 +285,22 @@ bool responseContentMatches(const PacketBuffer& response, const DNSName& qname,
     return false;
   }
 
-  uint16_t rqtype{};
-  uint16_t rqclass{};
-  DNSName rqname;
   try {
+    uint16_t rqtype{};
+    uint16_t rqclass{};
+    if (response.size() < (sizeof(dnsheader) + qname.wirelength() + sizeof(rqtype) + sizeof(rqclass))) {
+      return false;
+    }
+
     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
-    rqname = DNSName(reinterpret_cast<const char*>(response.data()), response.size(), sizeof(dnsheader), false, &rqtype, &rqclass);
+    const std::string_view packetView(reinterpret_cast<const char*>(response.data() + sizeof(dnsheader)), response.size() - sizeof(dnsheader));
+    if (qname.matches(packetView)) {
+      size_t pos = sizeof(dnsheader) + qname.wirelength();
+      rqtype = response.at(pos) * 256 + response.at(pos + 1);
+      rqclass = response.at(pos + 2) * 256 + response.at(pos + 3);
+      return rqtype == qtype && rqclass == qclass;
+    }
+    return false;
   }
   catch (const std::exception& e) {
     if (remote && !response.empty() && static_cast<size_t>(response.size()) > sizeof(dnsheader)) {
@@ -302,8 +312,6 @@ bool responseContentMatches(const PacketBuffer& response, const DNSName& qname,
     }
     return false;
   }
-
-  return rqtype == qtype && rqclass == qclass && rqname == qname;
 }
 
 static void restoreFlags(struct dnsheader* dnsHeader, uint16_t origFlags)
index fa4d20807b4cbd8a1a11fb491d2c87bba45d89d2..11ae9bf68b0cbab3b0651edeb31f39a471379ccd 100644 (file)
@@ -808,6 +808,23 @@ bool DNSName::RawLabelsVisitor::empty() const
   return d_position == 0;
 }
 
+bool DNSName::matches(const std::string_view& wire_uncompressed) const
+{
+  if (wire_uncompressed.empty() != empty() || wire_uncompressed.size() < d_storage.size()) {
+    return false;
+  }
+
+  const auto* us = d_storage.cbegin();
+  const auto* p = wire_uncompressed.cbegin();
+  for (; us != d_storage.cend() && p != wire_uncompressed.cend(); ++us, ++p) {
+    if (dns_tolower(*p) != dns_tolower(*us)) {
+      return false;
+    }
+  }
+
+  return us == d_storage.cend();
+}
+
 #if defined(PDNS_AUTH) // [
 std::ostream & operator<<(std::ostream &ostr, const ZoneName& zone)
 {
index b498b5e8a3a724c486ea1de24eedd7a7bcf574b2..a8fcf47f882db857fc9137952fed4ec395e26941 100644 (file)
@@ -115,6 +115,7 @@ public:
   bool isPartOf(const DNSName& rhs) const;   //!< Are we part of the rhs name? Note that name.isPartOf(name).
   inline bool operator==(const DNSName& rhs) const; //!< DNS-native comparison (case insensitive) - empty compares to empty
   bool operator!=(const DNSName& other) const { return !(*this == other); }
+  bool matches(const std::string_view& wire_uncompressed) const; // DNS-native (case insensitive) comparison against raw data in wire format
 
   std::string toString(const std::string& separator=".", const bool trailing=true) const;              //!< Our human-friendly, escaped, representation
   void toString(std::string& output, const std::string& separator=".", const bool trailing=true) const;
index 25523822b1fb5270331338dd0de55b4720401534..ea342e17c156bed786c5beea23f664cd00442b03 100644 (file)
@@ -1032,6 +1032,33 @@ BOOST_AUTO_TEST_CASE(test_getcommonlabels) {
   BOOST_CHECK_EQUAL(name5.getCommonLabels(name1), DNSName());
 }
 
+BOOST_AUTO_TEST_CASE(test_raw_data_comparison) {
+  const DNSName aroot("a.root-servers.net");
+  PacketBuffer query;
+  GenericDNSPacketWriter<PacketBuffer> packetWriter(query, aroot, QType::A, QClass::IN, 0);
+
+  {
+    const std::string_view raw(reinterpret_cast<const char*>(query.data()) + sizeof(dnsheader), query.size() - sizeof(dnsheader));
+    BOOST_CHECK(aroot.matches(raw));
+
+    DNSName differentCase("A.RooT-Servers.NET");
+    BOOST_CHECK(differentCase.matches(raw));
+
+    const DNSName broot("b.root-servers.net");
+    BOOST_CHECK(!(broot.matches(raw)));
+
+    /* last character differs */
+    const DNSName notaroot("a.root-servers.nes");
+    BOOST_CHECK(!(notaroot.matches(raw)));
+  }
+
+  {
+    /* too short */
+    const std::string_view raw(reinterpret_cast<const char*>(query.data() + sizeof(dnsheader)), aroot.wirelength() - 1);
+    BOOST_CHECK(!(aroot.matches(raw)));
+  }
+}
+
 #if defined(PDNS_AUTH)
 BOOST_AUTO_TEST_CASE(test_variantnames) {
   ZoneName zone1("..variant");