]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: add support to spoof a full self-generated response
authorCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Tue, 30 Nov 2021 15:00:26 +0000 (16:00 +0100)
committerCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Tue, 30 Nov 2021 15:00:26 +0000 (16:00 +0100)
pdns/dnsdist-lua-actions.cc
pdns/dnsdist-lua.hh
pdns/dnsdistdist/dnsdist-lua-ffi-interface.h
pdns/dnsdistdist/dnsdist-lua-ffi.cc
pdns/dnsdistdist/docs/rules-actions.rst
regression-tests.dnsdist/test_Spoofing.py

index 03475f79b95e08cea6e6df3dcf1fde816bcb7bc2..efaca54fddb3ad3a9c11c0a05013ecce12ea7d5f 100644 (file)
@@ -776,10 +776,18 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
   // do we even have a response?
   if (d_cname.empty() &&
       d_rawResponses.empty() &&
+      // make sure pre-forged response is greater than sizeof(dnsheader)
+      (d_raw.size() < sizeof(dnsheader)) &&
       d_types.count(qtype) == 0) {
     return Action::None;
   }
 
+  if (d_raw.size() >= sizeof(dnsheader)) {
+    auto id = dq->getHeader()->id;
+    dq->getMutableData() = std::move(d_raw);
+    dq->getHeader()->id = id;
+    return Action::HeaderModify;
+  }
   vector<ComboAddress> addrs;
   vector<std::string> rawResponses;
   unsigned int totrdatalen = 0;
@@ -2180,6 +2188,14 @@ void setupLuaActions(LuaContext& luaCtx)
       return ret;
     });
 
+  luaCtx.writeFunction("SpoofPacketAction", [](const std::string& response, size_t len) {
+    if (len < sizeof(dnsheader)) {
+      throw std::runtime_error(std::string("SpoofPacketAction: given packet len is too small"));
+    }
+    auto ret = std::shared_ptr<DNSAction>(new SpoofAction(response.c_str(), len));
+    return ret;
+    });
+
   luaCtx.writeFunction("DropAction", []() {
       return std::shared_ptr<DNSAction>(new DropAction);
     });
index b8230ebad60b0693256277a9e409a3b2ad37f2ce..1231b4f18785c8d2c5a90a051a1da9eb2d5da67f 100644 (file)
@@ -55,6 +55,10 @@ public:
   {
   }
 
+  SpoofAction(const char* rawresponse, size_t len): d_raw(rawresponse, rawresponse + len)
+  {
+  }
+
   SpoofAction(const vector<std::string>& raws): d_rawResponses(raws)
   {
   }
@@ -84,6 +88,7 @@ private:
   std::vector<ComboAddress> d_addrs;
   std::set<uint16_t> d_types;
   std::vector<std::string> d_rawResponses;
+  PacketBuffer d_raw;
   DNSName d_cname;
 };
 
index e371d9e1db1c7c2beaee83552c428c08f6f7b987..92f7cd10a110f39ee5f5a5277619a1efb6ac49e8 100644 (file)
@@ -107,6 +107,8 @@ void dnsdist_ffi_dnsquestion_send_trap(dnsdist_ffi_dnsquestion_t* dq, const char
 void dnsdist_ffi_dnsquestion_spoof_raw(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_ffi_raw_value_t* values, size_t valuesCount) __attribute__ ((visibility ("default")));
 // the content of values should contain raw IPv4 or IPv6 addresses in network byte-order
 void dnsdist_ffi_dnsquestion_spoof_addrs(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_ffi_raw_value_t* values, size_t valuesCount) __attribute__ ((visibility ("default")));
+// spoof raw response. will just replace qid to match question
+void dnsdist_ffi_dnsquestion_spoof_packet(dnsdist_ffi_dnsquestion_t* dq, const char* rawresponse, size_t len) __attribute__ ((visibility ("default")));
 
 typedef struct dnsdist_ffi_servers_list_t dnsdist_ffi_servers_list_t;
 typedef struct dnsdist_ffi_server_t dnsdist_ffi_server_t;
index 25e36c218f40eff4030e6ddc0fa8f6430d4b3b69..28a742ac7b0f6e1da76ec3f2f2e8eda2a43dcf12 100644 (file)
@@ -434,6 +434,13 @@ void dnsdist_ffi_dnsquestion_send_trap(dnsdist_ffi_dnsquestion_t* dq, const char
   }
 }
 
+void dnsdist_ffi_dnsquestion_spoof_packet(dnsdist_ffi_dnsquestion_t* dq, const char* raw, size_t len)
+{
+  std::string result;
+  SpoofAction sa(raw, len);
+  sa(dq->dq, &result);
+}
+
 void dnsdist_ffi_dnsquestion_spoof_raw(dnsdist_ffi_dnsquestion_t* dq, const dnsdist_ffi_raw_value_t* values, size_t valuesCount)
 {
   std::vector<std::string> data;
index fe322f52ff79048946e594cf8826e3b9b5cf4b39..07374e36131916d79d12e1c31ad14d6292038d2b 100644 (file)
@@ -1554,6 +1554,15 @@ The following actions exist.
   * ``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.
 
+.. function:: SpoofPacketAction(rawPacket, len)
+
+  .. versionadded:: 1.8.0
+
+  Spoof a raw self-generated answer
+
+  :param string rawPacket: The raw wire-ready DNS answer
+  :param int len: The len of the packet
+
 .. function:: TagAction(name, value)
 
   .. deprecated:: 1.6.0
index cc86e6bff3898bfa623354cf142fdaede27586fb..5ae66249b7efe5d15b0e7c9a93960c8d2c67de87 100644 (file)
@@ -913,3 +913,59 @@ class TestSpoofingLuaWithStatistics(DNSDistTest):
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
             self.assertTrue(receivedResponse)
             self.assertEqual(expectedResponseAfterwards, receivedResponse)
+
+class TestSpoofingLuaSpoofPacket(DNSDistTest):
+
+    _config_template = """
+    local ffi = require("ffi")
+
+    function spoofpacket(dq)
+        -- REFUSED answer
+        local rawResponse="\\000\\000\\129\\133\\000\\001\\000\\000\\000\\000\\000\\000\\014lua\\045raw\\045packet\\012ffi\\045spoofing\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001"
+        local qtype = ffi.C.dnsdist_ffi_dnsquestion_get_qtype(dq)
+        ffi.C.dnsdist_ffi_dnsquestion_spoof_packet(dq, rawResponse, string.len(rawResponse))
+        return DNSAction.HeaderModify
+    end
+
+    addAction("lua-raw-packet.ffi-spoofing.tests.powerdns.com.", LuaFFIAction(spoofpacket))
+    local otherResponse="\\000\\000\\129\\133\\000\\001\\000\\000\\000\\000\\000\\000\\014lua\\045raw\\045packet\\008spoofing\\005tests\\008powerdns\\003com\\000\\000\\001\\000\\001"
+    addAction("lua-raw-packet.spoofing.tests.powerdns.com.", SpoofPacketAction(otherResponse, string.len(otherResponse)))
+    newServer{address="127.0.0.1:%s"}
+    """
+    _verboseMode = True
+
+    def testLuaFFISpoofPacket(self):
+        """
+        Spoofing via Lua FFI: Spoof raw response via Lua FFI
+        """
+        name = 'lua-raw-packet.ffi-spoofing.tests.powerdns.com.'
+
+        #
+        query = dns.message.make_query(name, 'A', 'IN')
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags |= dns.flags.RA
+        expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEqual(expectedResponse, receivedResponse)
+
+    def testLuaSpoofPacket(self):
+        """
+        Spoofing via Lua : Spoof raw response via Lua
+        """
+        name = 'lua-raw-packet.spoofing.tests.powerdns.com.'
+
+        #
+        query = dns.message.make_query(name, 'A', 'IN')
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags |= dns.flags.RA
+        expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEqual(expectedResponse, receivedResponse)