]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Spoof a raw response for ANY queries
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 4 Dec 2023 16:09:52 +0000 (17:09 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 5 Dec 2023 10:06:34 +0000 (11:06 +0100)
This PR adds the ability to spoof a raw response for ``ANY`` queries, as
it would not make sense to use ``ANY`` for the type of the response record.

pdns/dnsdist-lua-actions.cc
pdns/dnsdist-lua-bindings-dnsquestion.cc
pdns/dnsdist-lua.hh
pdns/dnsdist.cc
pdns/dnsdistdist/dnsdist-lua-ffi.cc
pdns/dnsdistdist/docs/reference/dq.rst
pdns/dnsdistdist/docs/rules-actions.rst
regression-tests.dnsdist/test_Spoofing.py

index dabe0b8dac355009c81010aefcea99e8e6f2d7c5..736d9aef867a15b6bb02d22806aeca6fcd4d46ea 100644 (file)
@@ -943,6 +943,9 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
     });
   }
   else if (!rawResponses.empty()) {
+    if (qtype == QType::ANY && d_rawTypeForAny) {
+      qtype = *d_rawTypeForAny;
+    }
     qtype = htons(qtype);
     for(const auto& rawResponse : rawResponses){
       uint16_t rdataLen = htons(rawResponse.size());
@@ -2494,16 +2497,20 @@ void setupLuaActions(LuaContext& luaCtx)
 
   luaCtx.writeFunction("SpoofRawAction", [](LuaTypeOrArrayOf<std::string> inp, boost::optional<responseParams_t> vars) {
       vector<string> raws;
-      if(auto s = boost::get<std::string>(&inp)) {
-        raws.push_back(*s);
+      if (auto str = boost::get<std::string>(&inp)) {
+        raws.push_back(*str);
       } else {
-        const auto& v = boost::get<LuaArray<std::string>>(inp);
-        for(const auto& raw: v) {
+        const auto& vect = boost::get<LuaArray<std::string>>(inp);
+        for(const auto& raw: vect) {
           raws.push_back(raw.second);
         }
       }
-
-      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(raws));
+      uint32_t qtypeForAny{0};
+      getOptionalValue<uint32_t>(vars, "typeForAny", qtypeForAny);
+      if (qtypeForAny > std::numeric_limits<uint16_t>::max()) {
+        qtypeForAny = 0;
+      }
+      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(raws, qtypeForAny > 0 ? static_cast<uint16_t>(qtypeForAny) : std::optional<uint16_t>()));
       auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
       parseResponseConfig(vars, sa->d_responseConfig);
       checkAllParametersConsumed("SpoofRawAction", vars);
index 933d1720b9904e81327bddb3b26914a7bf46df6d..377f463062bdaca9e02283be053dd10bf14c545f 100644 (file)
@@ -227,7 +227,7 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
     return true;
   });
 
-  luaCtx.registerFunction<void(DNSQuestion::*)(const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>& response)>("spoof", [](DNSQuestion& dq, const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>& response) {
+  luaCtx.registerFunction<void(DNSQuestion::*)(const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>&, boost::optional<uint16_t>)>("spoof", [](DNSQuestion& dq, const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>& response, boost::optional<uint16_t> typeForAny) {
       if (response.type() == typeid(LuaArray<ComboAddress>)) {
           std::vector<ComboAddress> data;
           auto responses = boost::get<LuaArray<ComboAddress>>(response);
@@ -248,7 +248,7 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
             data.push_back(resp.second);
           }
           std::string result;
-          SpoofAction sa(data);
+          SpoofAction sa(data, typeForAny ? *typeForAny : std::optional<uint16_t>());
           sa(&dq, &result);
          return;
       }
index 7dfe0b30b9414984d022a13937c7862e209443f0..0037d6b9f0886e44b589427ca85f0876c4847ef4 100644 (file)
@@ -62,7 +62,7 @@ public:
   {
   }
 
-  SpoofAction(const vector<std::string>& raws): d_rawResponses(raws)
+  SpoofAction(const vector<std::string>& raws, std::optional<uint16_t> typeForAny): d_rawResponses(raws), d_rawTypeForAny(typeForAny)
   {
   }
 
@@ -93,6 +93,7 @@ private:
   std::vector<std::string> d_rawResponses;
   PacketBuffer d_raw;
   DNSName d_cname;
+  std::optional<uint16_t> d_rawTypeForAny{};
 };
 
 class LimitTTLResponseAction : public DNSResponseAction, public boost::noncopyable
index bfaa81bd9fa2cf3762e676c6f32f12b423a200bb..205afff95374df3008262951c604d07bdc8a4bf3 100644 (file)
@@ -863,7 +863,7 @@ static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent,
   if (raw) {
     std::vector<std::string> raws;
     stringtok(raws, spoofContent, ",");
-    SpoofAction sa(raws);
+    SpoofAction sa(raws, std::nullopt);
     sa(&dq, &result);
   }
   else {
index 2633d64644bf6723b28b85db7ac6a2198a7ced87..98e373159a6379443f9a6412b8f33cb2552cf2fc 100644 (file)
@@ -602,7 +602,7 @@ void dnsdist_ffi_dnsquestion_spoof_raw(dnsdist_ffi_dnsquestion_t* dq, const dnsd
   }
 
   std::string result;
-  SpoofAction sa(data);
+  SpoofAction sa(data, std::nullopt);
   sa(dq->dq, &result);
 }
 
index 4aa675fb1e3a1396e144fc521984579800487dd8..634ff81d3ff17ce2bf99fb30b78c381bbf7bf4da 100644 (file)
@@ -362,16 +362,20 @@ This state can be modified from the various hooks.
     :param string tail: The new data
     :returns: true if the operation succeeded, false otherwise
 
-  .. method:: DNSQuestion:spoof(ip|ips|raw|raws)
+  .. method:: DNSQuestion:spoof(ip|ips|raw|raws [, typeForAny])
 
     .. versionadded:: 1.6.0
 
+    .. versionchanged:: 1.9.0
+      Optional parameter ``typeForAny`` added.
+
     Forge a response with the specified record data as raw bytes. If you specify list of raws (it is assumed they match the query type), all will get spoofed in.
 
     :param ComboAddress ip: The `ComboAddress` to be spoofed, e.g. `newCA("192.0.2.1")`.
     :param table ComboAddresses ips: The `ComboAddress`es to be spoofed, e.g. `{ newCA("192.0.2.1"), newCA("192.0.2.2") }`.
     :param string raw: The raw string to be spoofed, e.g. `"\\192\\000\\002\\001"`.
     :param table raws: The raw strings to be spoofed, e.g. `{ "\\192\\000\\002\\001", "\\192\\000\\002\\002" }`.
+    :param int typeForAny: The type to use for raw responses when the requested type is ``ANY``, as using ``ANY` for the type of the response record would not make sense.
 
   .. method:: DNSQuestion:suspend(asyncID, queryID, timeoutMS) -> bool
 
index b4266f05aa9dbac12f2aa8d4642659c75a40d909..f02583f73c879bbe5b039cfd4a44821f4d966aa8 100644 (file)
@@ -1792,6 +1792,9 @@ The following actions exist.
   .. versionchanged:: 1.6.0
     Up to 1.6.0, it was only possible to spoof one answer.
 
+  .. versionchanged:: 1.9.0
+    Added the optional parameter ``typeForAny``.
+
   Forge a response with the specified raw bytes as record data.
 
   .. code-block:: Lua
@@ -1802,6 +1805,8 @@ The following actions exist.
     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``.
 
@@ -1828,6 +1833,7 @@ The following actions exist.
   * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
   * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
   * ``ttl``: int - The TTL of the record.
+  * ``typeForAny``: int - The record type to use when responding to queries of type ``ANY``, as using ``ANY`` for the type of the response record would not make sense.
 
 .. function:: SpoofSVCAction(svcParams [, options])
 
index 85478a788c95e5b6cff4c3dff63824b2e8f0ca3c..fafc94e1007ad8a2fb78884734330ace0afc7981 100644 (file)
@@ -19,6 +19,8 @@ class TestSpoofingSpoof(DNSDistTest):
     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:%s"}
     """
 
@@ -385,6 +387,29 @@ class TestSpoofingSpoof(DNSDistTest):
             self.assertEqual(expectedResponse, receivedResponse)
             self.assertEqual(receivedResponse.answer[0].ttl, 60)
 
+    def testSpoofRawANYAction(self):
+        """
+        Spoofing: Spoof a HINFO response for ANY queries
+        """
+        name = 'raw-any.spoofing.tests.powerdns.com.'
+
+        query = dns.message.make_query(name, 'ANY', 'IN')
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags &= ~dns.flags.AA
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.HINFO,
+                                    '"rfc8482" ""')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEqual(expectedResponse, receivedResponse)
+            self.assertEqual(receivedResponse.answer[0].ttl, 60)
 
     def testSpoofRawActionMulti(self):
         """