]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add option to spoofRawAction to spoof multiple answers 10063/head
authorSander Hoentjen <shoentjen@antagonist.nl>
Fri, 5 Feb 2021 11:07:22 +0000 (12:07 +0100)
committerSander Hoentjen <shoentjen@antagonist.nl>
Fri, 5 Feb 2021 13:55:30 +0000 (14:55 +0100)
pdns/dnsdist-console.cc
pdns/dnsdist-lua-actions.cc
pdns/dnsdist-lua.hh
pdns/dnsdist.cc
pdns/dnsdistdist/docs/rules-actions.rst
regression-tests.dnsdist/test_Spoofing.py

index e4de1815947c54f8c9a367257963b8ea1953b4c6..88e2de511ecf6b514bad51c1de55e8e256f5c9ee 100644 (file)
@@ -667,7 +667,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
   { "SpoofAction", true, "ip|list of ips [, options]", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
   { "SpoofCNAMEAction", true, "cname [, options]", "Forge a response with the specified CNAME value" },
-  { "SpoofRawAction", true, "raw [, options]", "Forge a response with the specified record data as raw bytes" },
+  { "SpoofRawAction", true, "raw|list of raws [, options]", "Forge a response with the specified record data as raw bytes. If you specify multiple raws (it is assumed they match the query type), all will get spoofed in" },
   { "SuffixMatchNodeRule", true, "smn[, quiet]", "Matches based on a group of domain suffixes for rapid testing of membership. Pass true as second parameter to prevent listing of all domains matched" },
   { "TagRule", true, "name [, value]", "matches if the tag named 'name' is present, with the given 'value' matching if any" },
   { "TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP" },
index c488e649fc65ad2221cd24961810f6680f1eef73..a8e1a4d5a9726edee4e6b4dd0b87991e7d9f2285 100644 (file)
@@ -540,21 +540,29 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
   uint16_t qtype = dq->qtype;
   // do we even have a response?
   if (d_cname.empty() &&
-      d_rawResponse.empty() &&
+      d_rawResponses.empty() &&
       d_types.count(qtype) == 0) {
     return Action::None;
   }
 
   vector<ComboAddress> addrs;
+  vector<std::string> rawResponses;
   unsigned int totrdatalen = 0;
   uint16_t numberOfRecords = 0;
   if (!d_cname.empty()) {
     qtype = QType::CNAME;
     totrdatalen += d_cname.getStorage().size();
     numberOfRecords = 1;
-  } else if (!d_rawResponse.empty()) {
-    totrdatalen += d_rawResponse.size();
-    numberOfRecords = 1;
+  } else if (!d_rawResponses.empty()) {
+    rawResponses.reserve(d_rawResponses.size());
+    for(const auto& rawResponse : d_rawResponses){
+      totrdatalen += rawResponse.size();
+      rawResponses.push_back(rawResponse);
+      ++numberOfRecords;
+    }
+    if (rawResponses.size() > 1) {
+      shuffle(rawResponses.begin(), rawResponses.end(), t_randomEngine);
+    }
   }
   else {
     for(const auto& addr : d_addrs) {
@@ -617,16 +625,21 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
     memcpy(dest, wireData.c_str(), wireData.length());
     dq->getHeader()->ancount++;
   }
-  else if (!d_rawResponse.empty()) {
-    uint16_t rdataLen = htons(d_rawResponse.size());
+  else if (!rawResponses.empty()) {
     qtype = htons(qtype);
-    memcpy(&recordstart[2], &qtype, sizeof(qtype));
-    memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
+    for(const auto& rawResponse : rawResponses){
+      uint16_t rdataLen = htons(rawResponse.size());
+      memcpy(&recordstart[2], &qtype, sizeof(qtype));
+      memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen));
 
-    memcpy(dest, recordstart, sizeof(recordstart));
-    dest += sizeof(recordstart);
-    memcpy(dest, d_rawResponse.c_str(), d_rawResponse.size());
-    dq->getHeader()->ancount++;
+      memcpy(dest, recordstart, sizeof(recordstart));
+      dest += sizeof(recordstart);
+
+      memcpy(dest, rawResponse.c_str(), rawResponse.size());
+      dest += rawResponse.size();
+
+      dq->getHeader()->ancount++;
+    }
     raw = true;
   }
   else {
@@ -1707,12 +1720,13 @@ void setupLuaActions(LuaContext& luaCtx)
 
   luaCtx.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<responseParams_t> vars) {
       vector<ComboAddress> addrs;
-      if(auto s = boost::get<std::string>(&inp))
+      if(auto s = boost::get<std::string>(&inp)) {
         addrs.push_back(ComboAddress(*s));
-      else {
+      else {
         const auto& v = boost::get<vector<pair<int,std::string>>>(inp);
-        for(const auto& a: v)
+        for(const auto& a: v) {
           addrs.push_back(ComboAddress(a.second));
+        }
       }
 
       auto ret = std::shared_ptr<DNSAction>(new SpoofAction(addrs));
@@ -1728,8 +1742,18 @@ void setupLuaActions(LuaContext& luaCtx)
       return ret;
     });
 
-  luaCtx.writeFunction("SpoofRawAction", [](const std::string& raw, boost::optional<responseParams_t> vars) {
-      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(raw));
+  luaCtx.writeFunction("SpoofRawAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<responseParams_t> vars) {
+      vector<string> raws;
+      if(auto s = boost::get<std::string>(&inp)) {
+        raws.push_back(*s);
+      } else {
+        const auto& v = boost::get<vector<pair<int,std::string>>>(inp);
+        for(const auto& raw: v) {
+          raws.push_back(raw.second);
+        }
+      }
+
+      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(raws));
       auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
       parseResponseConfig(vars, sa->d_responseConfig);
       return ret;
index 25d5e10c8e195b6f9c0ff02fc18382f6aadd4195..b40073613ab8dc99890f3b480419922e87369060 100644 (file)
@@ -55,7 +55,7 @@ public:
   {
   }
 
-  SpoofAction(const std::string& raw): d_rawResponse(raw)
+  SpoofAction(const vector<std::string>& raws): d_rawResponses(raws)
   {
   }
 
@@ -67,7 +67,7 @@ public:
     if (!d_cname.empty()) {
       ret += d_cname.toString() + " ";
     }
-    else if (!d_rawResponse.empty()) {
+    if (d_rawResponses.size() > 0) {
       ret += "raw bytes ";
     }
     else {
@@ -83,7 +83,7 @@ private:
   static thread_local std::default_random_engine t_randomEngine;
   std::vector<ComboAddress> d_addrs;
   std::set<uint16_t> d_types;
-  std::string d_rawResponse;
+  std::vector<std::string> d_rawResponses;
   DNSName d_cname;
 };
 
index 0856ec847ef1c518f8cdc71ffea0d71b96479bf8..d6c6aa9ac58455aad867ba6b79becc5437bf44bd 100644 (file)
@@ -735,7 +735,9 @@ static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent,
   string result;
 
   if (raw) {
-    SpoofAction sa(spoofContent);
+    std::vector<std::string> raws;
+    stringtok(raws, spoofContent, ",");
+    SpoofAction sa(raws);
     sa(&dq, &result);
   }
   else {
index fa860d65163e97fa8bb657d6ecd6f774df11d4b1..6749e45361c09838894a437c46cd2f48a46dac30 100644 (file)
@@ -1373,15 +1373,19 @@ The following actions exist.
   * ``ttl``: int - The TTL of the record.
 
 .. function:: SpoofRawAction(rawAnswer [, options])
+              SpoofRawAction(rawAnswers [, options])
 
   .. versionadded:: 1.5.0
 
+  .. versionchanged:: 1.6.0
+    Up to 1.6.0, it was only possible to spoof one answer.
+
   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 a "aaa" "bbb" TXT record:
-    addAction(AndRule({QNameRule('raw.powerdns.com.'), QTypeRule(DNSQType.TXT)}), SpoofRawAction("\003aaa\004bbbb"))
+    -- 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'
@@ -1390,6 +1394,7 @@ The following actions exist.
   :func:`DNSName:toDNSString` is convenient for converting names to wire format for passing to ``SpoofRawAction``.
 
   :param string rawAnswer: The raw record data
+  :param {string} rawAnswers: A table of raw record data to spoof
   :param table options: A table with key: value pairs with options.
 
   Options:
index ff2675c313e2fba8cb8adec728dff4a822ef5de1..479a4cf8767533c0517ff7b4fad7f3d1113bf45c 100644 (file)
@@ -16,6 +16,8 @@ class TestSpoofingSpoof(DNSDistTest):
     addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction("\\192\\000\\002\\001"))
     addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction("\\003aaa\\004bbbb\\011ccccccccccc"))
     addAction(AndRule{makeRule("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{makeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction({"\\003aaa\\004bbbb", "\\011ccccccccccc"}))
+    addAction(AndRule{makeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction({"\\192\\000\\002\\001", "\\192\\000\\002\\002"}))
     newServer{address="127.0.0.1:%s"}
     """
 
@@ -357,6 +359,50 @@ class TestSpoofingSpoof(DNSDistTest):
             self.assertEquals(expectedResponse, receivedResponse)
             self.assertEquals(receivedResponse.answer[0].ttl, 3600)
 
+    def testSpoofRawActionMulti(self):
+        """
+        Spoofing: Spoof a response from several raw bytes
+        """
+        name = 'multiraw.spoofing.tests.powerdns.com.'
+
+        # A
+        query = dns.message.make_query(name, 'A', '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.A,
+                                    '192.0.2.1', '192.0.2.2')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+            self.assertEquals(receivedResponse.answer[0].ttl, 60)
+
+        # TXT
+        query = dns.message.make_query(name, 'TXT', '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.TXT,
+                                    '"aaa" "bbbb"', '"ccccccccccc"')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+            self.assertEquals(receivedResponse.answer[0].ttl, 60)
+
 class TestSpoofingLuaSpoof(DNSDistTest):
 
     _config_template = """