]> 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, 22 Aug 2025 13:12:32 +0000 (15:12 +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>
(cherry picked from commit 67eb73850f3141c44963d95ef815fe6a0586d2a8)
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 56ced819b16d68b4cfc364d6c27e4d242c82d36a..f1a3e6dc5494562b6243c73489a32e2242b10b01 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 13c5cdb19e0b3826f2928f0bd2c72849cbb8e510..5eb6dd8bf4e8d90234504b12839354fc8853e6a0 100644 (file)
@@ -732,6 +732,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 630e9be4d8511e791baf685bbe186d597586e30d..8d7a9571e7c68c2f8cabae5a6b3112ca147f4345 100644 (file)
@@ -112,6 +112,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 a478e59bade78a03fad2cdaade6d17841a3771d1..3e7cab573f6cc11fe946cdf40a582c6a94a18747 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");