]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Handle escaped values in YAML SpoofRaw parameters 16661/head
authorRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 16 Dec 2025 16:48:54 +0000 (17:48 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 18 Dec 2025 09:16:36 +0000 (10:16 +0100)
Signed-off-by: Remi Gacogne <remi.gacogne@powerdns.com>
pdns/dnsdistdist/dnsdist-actions-definitions.yml
pdns/dnsdistdist/dnsdist-configuration-yaml.cc
pdns/dnsdistdist/dnsdist-rules.cc
regression-tests.dnsdist/test_Spoofing.py

index a9ea1c3ca690fa807978a6c177bbb926ae74fd92..eb78216e1a56cd0a689a163511a0d476d96dde6c 100644 (file)
@@ -459,19 +459,7 @@ are processed after this action"
       description: "The length of the DNS packet"
 - name: "SpoofRaw"
   description: |
-               Forge a response with the specified raw bytes as record data
-               .. code-block:: Lua
-
-                 -- select queries for the 'raw.powerdns.com.' name and TXT type, and answer with both a "aaa" "bbbb" and "ccc" TXT record:
-                 addAction(AndRule({QNameRule('raw.powerdns.com.'), QTypeRule(DNSQType.TXT)}), SpoofRawAction({"\003aaa\004bbbb", "\003ccc"}))
-                 -- select queries for the 'raw-srv.powerdns.com.' name and SRV type, and answer with a '0 0 65535 srv.powerdns.com.' SRV record, setting the AA bit to 1 and the TTL to 3600s
-                 addAction(AndRule({QNameRule('raw-srv.powerdns.com.'), QTypeRule(DNSQType.SRV)}), SpoofRawAction("\000\000\000\000\255\255\003srv\008powerdns\003com\000", { aa=true, ttl=3600 }))
-                 -- select reverse queries for '127.0.0.1' and answer with 'localhost'
-                 addAction(AndRule({QNameRule('1.0.0.127.in-addr.arpa.'), QTypeRule(DNSQType.PTR)}), SpoofRawAction("\009localhost\000"))
-                 -- rfc8482: Providing Minimal-Sized Responses to DNS Queries That Have QTYPE=ANY via HINFO of value "rfc8482"
-                 addAction(QTypeRule(DNSQType.ANY), SpoofRawAction("\007rfc\056\052\056\050\000", { typeForAny=DNSQType.HINFO }))
-
-               :func:`DNSName:toDNSString` is convenient for converting names to wire format for passing to ``SpoofRawAction``.
+               Forge a response with the specified raw bytes as record data. Non-character values should be encoded in the ``\DDD`` format where ``DDD`` is the decimal value. For example to wire content of an A record containing ``1.2.3.4`` should be encoded as ``\001\002\003\004``.
 
                ``sdig dumpluaraw`` and ``pdnsutil raw-lua-from-content`` from PowerDNS can generate raw answers for you:
 
index 112de6227cda078d292c75c78e5b4413159b52ae..09af41a71f2a81ba5fcd1f2ab8fc39e04a7a5c3e 100644 (file)
@@ -106,7 +106,7 @@ template <class T>
 static T checkedConversionFromStr(const std::string& context, const std::string& parameterName, const std::string& str)
 {
   try {
-    return pdns::checked_stoi<T>(std::string(str));
+    return pdns::checked_stoi<T>(str);
   }
   catch (const std::exception& exp) {
     throw std::runtime_error("Error converting value '" + str + "' for parameter '" + parameterName + "' in YAML directive '" + context + "': " + exp.what());
@@ -131,6 +131,30 @@ static bool getOptionalLuaFunction(T& destination, const ::rust::string& functio
   return true;
 }
 
+static std::string rustStringWithEscapedRawContentToString(const ::rust::String& sourceRust)
+{
+  const std::string source(sourceRust);
+  std::string destination;
+  destination.reserve(source.size());
+
+  auto start = source.begin();
+  auto position = std::find(start, source.end(), '\\');
+  while (position < source.end() && std::distance(position, source.end()) >= 4) {
+    destination.insert(destination.end(), start, position);
+    start = position + 4;
+    auto escaped = std::string(position + 1, position + 4);
+    auto code = checkedConversionFromStr<uint8_t>("SpoofRaw", "answers", escaped);
+    destination.insert(destination.end(), static_cast<char>(code));
+    position = std::find(start, source.end(), '\\');
+  }
+
+  if (start < source.end()) {
+    destination.insert(destination.end(), start, source.end());
+  }
+
+  return destination;
+}
+
 static uint8_t strToRCode(const std::string& context, const std::string& parameterName, const ::rust::String& rcode_rust_string)
 {
   auto rcode_str = std::string(rcode_rust_string);
@@ -446,7 +470,7 @@ static std::shared_ptr<DownstreamState> createBackendFromConfiguration(const dns
   }
   backendConfig.checkType = std::string(hcConf.qtype);
   if (!hcConf.qclass.empty()) {
-    backendConfig.checkClass = QClass(std::string(hcConf.qclass));
+    backendConfig.checkClass = QClass(boost::to_upper_copy(std::string(hcConf.qclass)));
   }
   backendConfig.checkTimeout = hcConf.timeout;
   backendConfig.d_tcpCheck = hcConf.use_tcp;
@@ -1463,7 +1487,7 @@ std::shared_ptr<DNSActionWrapper> getSpoofRawAction(const SpoofRawActionConfigur
 {
   std::vector<std::string> raws;
   for (const auto& answer : config.answers) {
-    raws.emplace_back(answer);
+    raws.emplace_back(dnsdist::configuration::yaml::rustStringWithEscapedRawContentToString(answer));
   }
   std::optional<uint16_t> qtypeForAny;
   if (!config.qtype_for_any.empty()) {
index 97b31bf9b965be1d7e6163f7958ef21e8bab6f5b..61536e567eb32310672a0a57c8761a84a262b494 100644 (file)
@@ -143,7 +143,7 @@ std::shared_ptr<QClassRule> getQClassSelector(const std::string& qclassStr, uint
 {
   QClass qclass(qclassCode);
   if (!qclassStr.empty()) {
-    qclass = QClass(std::string(qclassStr));
+    qclass = QClass(boost::to_upper_copy(std::string(qclassStr)));
   }
 
   return std::make_shared<QClassRule>(qclass);
index a749272b1afc21b9d1c789f8dd8065fe783f66a5..332615922af233191882f988a09030f2576a57f9 100644 (file)
@@ -2,27 +2,7 @@
 import dns
 from dnsdisttests import DNSDistTest
 
-class TestSpoofingSpoof(DNSDistTest):
-
-    _config_template = """
-    addAction(SuffixMatchNodeRule("spoofaction.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}))
-    addAction(SuffixMatchNodeRule("spoofaction-aa.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {aa=true}))
-    addAction(SuffixMatchNodeRule("spoofaction-ad.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ad=true}))
-    addAction(SuffixMatchNodeRule("spoofaction-ra.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ra=true}))
-    addAction(SuffixMatchNodeRule("spoofaction-nora.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ra=false}))
-    addAction(SuffixMatchNodeRule("spoofaction-ttl.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ttl=1500}))
-    addAction(SuffixMatchNodeRule("cnamespoofaction.spoofing.tests.powerdns.com."), SpoofCNAMEAction("cnameaction.spoofing.tests.powerdns.com."))
-    addAction("multispoof.spoofing.tests.powerdns.com", SpoofAction({"192.0.2.1", "192.0.2.2", "2001:DB8::1", "2001:DB8::2"}))
-    addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction("\\192\\000\\002\\001"))
-    addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction("\\003aaa\\004bbbb\\011ccccccccccc"))
-    addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.SRV)}, SpoofRawAction("\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000", { aa=true, ttl=3600 }))
-    addAction(AndRule{SuffixMatchNodeRule("rawchaos.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT), QClassRule(DNSClass.CHAOS)}, SpoofRawAction("\\005chaos"))
-    addAction(AndRule{SuffixMatchNodeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction({"\\003aaa\\004bbbb", "\\011ccccccccccc"}))
-    addAction(AndRule{SuffixMatchNodeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction({"\\192\\000\\002\\001", "\\192\\000\\002\\002"}))
-    -- rfc8482
-    addAction(AndRule{SuffixMatchNodeRule("raw-any.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.ANY)}, SpoofRawAction("\\007rfc\\056\\052\\056\\050\\000", { typeForAny=DNSQType.HINFO }))
-    newServer{address="127.0.0.1:%d"}
-    """
+class SpoofingTests(object):
 
     def testSpoofActionA(self):
         """
@@ -311,7 +291,7 @@ class TestSpoofingSpoof(DNSDistTest):
         expectedResponse = dns.message.make_response(query)
         expectedResponse.flags |= dns.flags.RA
         rrset = dns.rrset.from_text(name,
-                                    60,
+                                    1500,
                                     dns.rdataclass.IN,
                                     dns.rdatatype.AAAA,
                                     '2001:DB8::1')
@@ -423,7 +403,7 @@ class TestSpoofingSpoof(DNSDistTest):
                                     60,
                                     dns.rdataclass.CH,
                                     dns.rdatatype.TXT,
-                                    '"chaos"')
+                                    '"chaos\\\\test"')
         expectedResponse.answer.append(rrset)
 
         for method in ("sendUDPQuery", "sendTCPQuery"):
@@ -501,6 +481,237 @@ class TestSpoofingSpoof(DNSDistTest):
             self.checkMessageNoEDNS(expectedResponse, receivedResponse)
             self.assertEqual(receivedResponse.answer[0].ttl, 60)
 
+class TestSpoofingViaLuaConfig(DNSDistTest, SpoofingTests):
+
+    _config_template = """
+    addAction(SuffixMatchNodeRule("spoofaction.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}))
+    addAction(SuffixMatchNodeRule("spoofaction-aa.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {aa=true}))
+    addAction(SuffixMatchNodeRule("spoofaction-ad.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ad=true}))
+    addAction(SuffixMatchNodeRule("spoofaction-ra.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ra=true}))
+    addAction(SuffixMatchNodeRule("spoofaction-nora.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ra=false}))
+    addAction(SuffixMatchNodeRule("spoofaction-ttl.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ttl=1500}))
+    addAction(SuffixMatchNodeRule("cnamespoofaction.spoofing.tests.powerdns.com."), SpoofCNAMEAction("cnameaction.spoofing.tests.powerdns.com."))
+    addAction("multispoof.spoofing.tests.powerdns.com", SpoofAction({"192.0.2.1", "192.0.2.2", "2001:DB8::1", "2001:DB8::2"}))
+    addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction("\\192\\000\\002\\001"))
+    addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction("\\003aaa\\004bbbb\\011ccccccccccc"))
+    addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.SRV)}, SpoofRawAction("\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000", { aa=true, ttl=3600 }))
+    addAction(AndRule{SuffixMatchNodeRule("rawchaos.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT), QClassRule(DNSClass.CHAOS)}, SpoofRawAction("\\010chaos\\\\test"))
+    addAction(AndRule{SuffixMatchNodeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction({"\\003aaa\\004bbbb", "\\011ccccccccccc"}))
+    addAction(AndRule{SuffixMatchNodeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction({"\\192\\000\\002\\001", "\\192\\000\\002\\002"}))
+    -- rfc8482
+    addAction(AndRule{SuffixMatchNodeRule("raw-any.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.ANY)}, SpoofRawAction("\\007rfc\\056\\052\\056\\050\\000", { typeForAny=DNSQType.HINFO }))
+    newServer{address="127.0.0.1:%d"}
+    """
+
+class TestSpoofingViaYamlConfig(DNSDistTest, SpoofingTests):
+
+    _yaml_config_template = """
+backends:
+  - address: "127.0.0.1:%d"
+    protocol: Do53
+
+query_rules:
+  - selector:
+      type: "QNameSuffix"
+      suffixes:
+        - "spoofaction.spoofing.tests.powerdns.com."
+    action:
+      type: "Spoof"
+      ips:
+        - 192.0.2.1
+        - 2001:DB8::1
+      vars:
+        ttl: 60
+  - selector:
+      type: "QNameSuffix"
+      suffixes:
+        - "spoofaction-aa.spoofing.tests.powerdns.com."
+    action:
+      type: "Spoof"
+      ips:
+        - 192.0.2.1
+        - 2001:DB8::1
+      vars:
+        set_aa: true
+        ttl: 60
+  - selector:
+      type: "QNameSuffix"
+      suffixes:
+        - "spoofaction-ad.spoofing.tests.powerdns.com."
+    action:
+      type: "Spoof"
+      ips:
+        - 192.0.2.1
+        - 2001:DB8::1
+      vars:
+        set_ad: true
+        ttl: 60
+  - selector:
+      type: "QNameSuffix"
+      suffixes:
+        - "spoofaction-ra.spoofing.tests.powerdns.com."
+    action:
+      type: "Spoof"
+      ips:
+        - 192.0.2.1
+        - 2001:DB8::1
+      vars:
+        set_ra: true
+        ttl: 60
+  - selector:
+      type: "QNameSuffix"
+      suffixes:
+        - "spoofaction-nora.spoofing.tests.powerdns.com."
+    action:
+      type: "Spoof"
+      ips:
+        - 192.0.2.1
+        - 2001:DB8::1
+      vars:
+        set_ra: false
+        ttl: 60
+  - selector:
+      type: "QNameSuffix"
+      suffixes:
+        - "spoofaction-ttl.spoofing.tests.powerdns.com."
+    action:
+      type: "Spoof"
+      ips:
+        - 192.0.2.1
+        - 2001:DB8::1
+      vars:
+        set_ra: true
+        ttl: 1500
+  - selector:
+      type: "QNameSuffix"
+      suffixes:
+        - "cnamespoofaction.spoofing.tests.powerdns.com."
+    action:
+      type: "SpoofCNAME"
+      cname: cnameaction.spoofing.tests.powerdns.com.
+      vars:
+        ttl: 60
+  - selector:
+      type: "QNameSuffix"
+      suffixes:
+        - "multispoof.spoofing.tests.powerdns.com"
+    action:
+      type: "Spoof"
+      ips:
+        - 192.0.2.1
+        - 192.0.2.2
+        - 2001:DB8::1
+        - 2001:DB8::2
+      vars:
+        ttl: 60
+  - selector:
+      type: "And"
+      selectors:
+        - type: "QNameSuffix"
+          suffixes:
+            - "raw.spoofing.tests.powerdns.com"
+        - type: "QType"
+          qtype: "A"
+    action:
+      type: "SpoofRaw"
+      answers:
+        - '\\192\\000\\002\\001'
+      vars:
+        ttl: 60
+  - selector:
+      type: "And"
+      selectors:
+        - type: "QNameSuffix"
+          suffixes:
+            - "raw.spoofing.tests.powerdns.com"
+        - type: "QType"
+          qtype: "TXT"
+    action:
+      type: "SpoofRaw"
+      answers:
+        - '\\003aaa\\004bbbb\\011ccccccccccc'
+      vars:
+        ttl: 60
+  - selector:
+      type: "And"
+      selectors:
+        - type: "QNameSuffix"
+          suffixes:
+            - "raw.spoofing.tests.powerdns.com"
+        - type: "QType"
+          qtype: "SRV"
+    action:
+      type: "SpoofRaw"
+      answers:
+        - '\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000'
+      vars:
+        set_aa: true
+        ttl: 3600
+  - selector:
+      type: "And"
+      selectors:
+        - type: "QNameSuffix"
+          suffixes:
+            - "rawchaos.spoofing.tests.powerdns.com"
+        - type: "QType"
+          qtype: "TXT"
+        - type: "QClass"
+          qclass: "chaos"
+    action:
+      type: "SpoofRaw"
+      answers:
+        - '\\010chaos\\092test'
+      vars:
+        ttl: 60
+  - selector:
+      type: "And"
+      selectors:
+        - type: "QNameSuffix"
+          suffixes:
+            - "multiraw.spoofing.tests.powerdns.com"
+        - type: "QType"
+          qtype: "TXT"
+    action:
+      type: "SpoofRaw"
+      answers:
+        - '\\003aaa\\004bbbb'
+        - '\\011ccccccccccc'
+      vars:
+        ttl: 60
+  - selector:
+      type: "And"
+      selectors:
+        - type: "QNameSuffix"
+          suffixes:
+            - "multiraw.spoofing.tests.powerdns.com"
+        - type: "QType"
+          qtype: "A"
+    action:
+      type: "SpoofRaw"
+      answers:
+        - '\\192\\000\\002\\001'
+        - '\\192\\000\\002\\002'
+      vars:
+        ttl: 60
+  - selector:
+      type: "And"
+      selectors:
+        - type: "QNameSuffix"
+          suffixes:
+            - "raw-any.spoofing.tests.powerdns.com"
+        - type: "QType"
+          qtype: "ANY"
+    action:
+      type: "SpoofRaw"
+      qtype_for_any: 'HINFO'
+      answers:
+        - '\\007rfc\\056\\052\\056\\050\\000'
+      vars:
+        ttl: 60
+    """
+    _yaml_config_params = ['_testServerPort']
+    _config_params = []
+
 class TestSpoofingLuaSpoof(DNSDistTest):
 
     _config_template = """
@@ -520,9 +731,6 @@ class TestSpoofingLuaSpoof(DNSDistTest):
         return DNSAction.Spoof, "spoofedcname.spoofing.tests.powerdns.com."
     end
 
-    addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction("\\003aaa\\004bbbb\\011ccccccccccc"))
-    addAction(AndRule{SuffixMatchNodeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.SRV)}, SpoofRawAction("\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000", { aa=true, ttl=3600 }))
-
     function spoofrawrule(dq)
         if dq.qtype == DNSQType.A then
              return DNSAction.SpoofRaw, "\\192\\000\\002\\001"