]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: add lua support to limit reponse TTL values
authorCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Wed, 1 Dec 2021 13:57:46 +0000 (14:57 +0100)
committerCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Wed, 1 Dec 2021 13:57:46 +0000 (14:57 +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/dnsdist-lua-ffi.hh
pdns/dnsdistdist/docs/rules-actions.rst
regression-tests.dnsdist/test_Responses.py

index 03475f79b95e08cea6e6df3dcf1fde816bcb7bc2..ec1450d6984d1833c71aaf83c0655681fa802cd6 100644 (file)
@@ -1233,7 +1233,6 @@ private:
   bool d_buffered{true};
 };
 
-
 class SetDisableValidationAction : public DNSAction
 {
 public:
@@ -2217,6 +2216,18 @@ void setupLuaActions(LuaContext& luaCtx)
       return std::shared_ptr<DNSResponseAction>(new LogResponseAction(fname ? *fname : "", append ? *append : false, buffered ? *buffered : false, verboseOnly ? *verboseOnly : true, includeTimestamp ? *includeTimestamp : false));
     });
 
+  luaCtx.writeFunction("LimitTTLResponseAction", [](uint32_t min, uint32_t max) {
+      return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(min, max));
+    });
+
+  luaCtx.writeFunction("SetMinTTLResponseAction", [](uint32_t min) {
+      return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(min));
+    });
+
+  luaCtx.writeFunction("SetMaxTTLResponseAction", [](uint32_t max) {
+      return std::shared_ptr<DNSResponseAction>(new LimitTTLResponseAction(0, max));
+    });
+
   luaCtx.writeFunction("RCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
       auto ret = std::shared_ptr<DNSAction>(new RCodeAction(rcode));
       auto rca = std::dynamic_pointer_cast<RCodeAction>(ret);
index b8230ebad60b0693256277a9e409a3b2ad37f2ce..0e630eb2cfc0caabcf4824e02d029dc07caccd42 100644 (file)
@@ -21,6 +21,7 @@
  */
 #pragma once
 
+#include "dnsparser.hh"
 #include <random>
 
 struct ResponseConfig
@@ -87,6 +88,42 @@ private:
   DNSName d_cname;
 };
 
+class LimitTTLResponseAction : public DNSResponseAction, public boost::noncopyable
+{
+public:
+  LimitTTLResponseAction() {}
+
+  LimitTTLResponseAction(uint32_t min, uint32_t max = std::numeric_limits<uint32_t>::max()) : d_min(min), d_max(max)
+  {
+  }
+
+  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+  {
+    auto visitor = [&](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl) {
+      if (d_min > 0) {
+        if (ttl < d_min) {
+          ttl = d_min;
+        }
+                       }
+      if (ttl > d_max) {
+        ttl = d_max;
+      }
+      return ttl;
+    };
+    editDNSPacketTTL(reinterpret_cast<char *>(dr->getMutableData().data()), dr->getData().size(), visitor);
+    return DNSResponseAction::Action::None;
+  }
+
+  std::string toString() const override
+  {
+    return "limit ttl";
+  }
+
+private:
+  uint32_t d_min{0};
+  uint32_t d_max{std::numeric_limits<uint32_t>::max()};
+};
+
 typedef boost::variant<string, vector<pair<int, string>>, std::shared_ptr<DNSRule>, DNSName, vector<pair<int, DNSName> > > luadnsrule_t;
 std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var);
 typedef std::unordered_map<std::string, boost::variant<std::string> > luaruleparams_t;
index e371d9e1db1c7c2beaee83552c428c08f6f7b987..021de37d9a82f08f7631af67f8e3c72c2d041b1a 100644 (file)
@@ -24,6 +24,7 @@
    is passed to the Lua FFI wrapper which doesn't support it */
 
 typedef struct dnsdist_ffi_dnsquestion_t dnsdist_ffi_dnsquestion_t;
+typedef struct dnsdist_ffi_dnsresponse_t dnsdist_ffi_dnsresponse_t;
 typedef struct dnsdist_ffi_servers_list_t dnsdist_ffi_servers_list_t;
 typedef struct dnsdist_ffi_server_t dnsdist_ffi_server_t;
 
@@ -123,3 +124,7 @@ const char* dnsdist_ffi_server_get_name_with_addr(const dnsdist_ffi_server_t* se
 int dnsdist_ffi_server_get_weight(const dnsdist_ffi_server_t* server) __attribute__ ((visibility ("default")));
 int dnsdist_ffi_server_get_order(const dnsdist_ffi_server_t* server) __attribute__ ((visibility ("default")));
 double dnsdist_ffi_server_get_latency(const dnsdist_ffi_server_t* server) __attribute__ ((visibility ("default")));
+
+void dnsdist_ffi_dnsresponse_set_min_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t min) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_dnsresponse_set_max_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t max) __attribute__ ((visibility ("default")));
+void dnsdist_ffi_dnsresponse_limit_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t min, uint32_t max) __attribute__ ((visibility ("default")));
index 25e36c218f40eff4030e6ddc0fa8f6430d4b3b69..ac6676c586a28bc1ffd0ec412e6c42bf98bc58ab 100644 (file)
@@ -544,6 +544,23 @@ const char* dnsdist_ffi_server_get_name_with_addr(const dnsdist_ffi_server_t* se
   return server->server->getNameWithAddr().c_str();
 }
 
+void dnsdist_ffi_dnsresponse_set_min_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t min)
+{
+  dnsdist_ffi_dnsresponse_limit_ttl(dr, min, std::numeric_limits<uint32_t>::max());
+}
+
+void dnsdist_ffi_dnsresponse_set_max_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t max)
+{
+  dnsdist_ffi_dnsresponse_limit_ttl(dr, 0, max);
+}
+
+void dnsdist_ffi_dnsresponse_limit_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t min, uint32_t max)
+{
+  std::string result;
+  LimitTTLResponseAction ac(min, max);
+  ac(dr->dr, &result);
+}
+
 const std::string& getLuaFFIWrappers()
 {
   static const std::string interface =
index 103065950082b585853a7953220dada1707a1692..91877bbb4f27fc0670d311656fd0b23cf28cf761 100644 (file)
@@ -59,6 +59,15 @@ struct dnsdist_ffi_dnsquestion_t
   boost::optional<std::string> httpScheme{boost::none};
 };
 
+struct dnsdist_ffi_dnsresponse_t
+{
+  dnsdist_ffi_dnsresponse_t(DNSResponse* dr_): dr(dr_)
+  {
+  }
+
+  DNSResponse* dr{nullptr};
+};
+
 // dnsdist_ffi_server_t is a lightuserdata
 template<>
 struct LuaContext::Pusher<dnsdist_ffi_server_t*> {
index fe322f52ff79048946e594cf8826e3b9b5cf4b39..d82e2c05b63d10c6114f97d781ed3dd78c1433ac 100644 (file)
@@ -819,6 +819,7 @@ Some actions allow further processing of rules, this is noted in their descripti
 - :func:`KeyValueStoreLookupAction`
 - :func:`DnstapLogAction`
 - :func:`DnstapLogResponseAction`
+- :func:`LimitTTLResponseAction`
 - :func:`LogAction`
 - :func:`NoneAction`
 - :func:`RemoteLogAction`
@@ -1001,6 +1002,15 @@ The following actions exist.
   :param KeyValueLookupKey lookupKey: The key to use for the lookup
   :param string destinationTag: The name of the tag to store the result into
 
+.. function:: LimitTTLResponseAction(min[, max])
+
+  .. versionadded:: 1.8.0
+
+  Cap the TTLs of the response to the given boundaries.
+
+  :param int min: The minimum allowed value
+  :param int max: The maximum allowed value
+
 .. function:: LogAction([filename[, binary[, append[, buffered[, verboseOnly[, includeTimestamp]]]]]])
 
   .. versionchanged:: 1.4.0
@@ -1328,6 +1338,22 @@ The following actions exist.
 
   :param int option: The EDNS0 option number
 
+.. function:: SetMaxTTLResponseAction(max)
+
+  .. versionadded:: 1.8.0
+
+  Cap the TTLs of the response to the given maximum.
+
+  :param int max: The maximum allowed value
+
+.. function:: SetMinTTLResponseAction(min)
+
+  .. versionadded:: 1.8.0
+
+  Cap the TTLs of the response to the given minimum.
+
+  :param int min: The minimum allowed value
+
 .. function:: SetNoRecurseAction()
 
   .. versionadded:: 1.6.0
index 0edad08b2c9fd30739668a162dd2c3dd8f6ba120..b887ab661f6070b2d0e6890cd80b7307c8e5e813 100644 (file)
@@ -233,6 +233,118 @@ class TestResponseRuleEditTTL(DNSDistTest):
             self.assertNotEqual(response.answer[0].ttl, receivedResponse.answer[0].ttl)
             self.assertEqual(receivedResponse.answer[0].ttl, self._ttl)
 
+class TestResponseRuleLimitTTL(DNSDistTest):
+
+    _lowttl = 60
+    _defaulttl = 3600
+    _highttl = 18000
+    _config_params = ['_lowttl', '_highttl', '_testServerPort']
+    _config_template = """
+    local ffi = require("ffi")
+    local lowttl = %d
+    local highttl = %d
+
+    function luaFFISetMinTTL(dr)
+      ffi.C.dnsdist_ffi_dnsresponse_set_min_ttl(dr, highttl)
+      return DNSAction.None, ""
+    end
+    function luaFFISetMaxTTL(dr)
+      ffi.C.dnsdist_ffi_dnsresponse_set_max_ttl(dr, lowttl)
+      return DNSAction.None, ""
+    end
+
+    newServer{address="127.0.0.1:%s"}
+
+    addResponseAction("min.responses.tests.powerdns.com.", SetMinTTLResponseAction(highttl))
+    addResponseAction("max.responses.tests.powerdns.com.", SetMaxTTLResponseAction(lowttl))
+    addResponseAction("ffi.min.limitttl.responses.tests.powerdns.com.", LuaResponseAction(luaFFISetMinTTL))
+    addResponseAction("ffi.max.limitttl.responses.tests.powerdns.com.", LuaResponseAction(luaFFISetMaxTTL))
+    """
+
+    def testLimitTTL(self):
+        """
+        Responses: Alter the TTLs via Limiter
+        """
+        name = 'min.responses.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        response.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(response, receivedResponse)
+            self.assertNotEqual(response.answer[0].ttl, receivedResponse.answer[0].ttl)
+            self.assertEqual(receivedResponse.answer[0].ttl, self._highttl)
+
+        name = 'max.responses.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        response.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(response, receivedResponse)
+            self.assertNotEqual(response.answer[0].ttl, receivedResponse.answer[0].ttl)
+            self.assertEqual(receivedResponse.answer[0].ttl, self._lowttl)
+
+    def testLimitTTLFFI(self):
+        """
+        Responses: Alter the TTLs via Limiter
+        """
+        name = 'ffi.min.responses.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        response.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(response, receivedResponse)
+            self.assertNotEqual(response.answer[0].ttl, receivedResponse.answer[0].ttl)
+            self.assertEqual(receivedResponse.answer[0].ttl, self._highttl)
+
+        name = 'ffi.max.responses.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        response.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.assertEqual(response, receivedResponse)
+            self.assertNotEqual(response.answer[0].ttl, receivedResponse.answer[0].ttl)
+            self.assertEqual(receivedResponse.answer[0].ttl, self._lowttl)
+
 class TestResponseLuaActionReturnSyntax(DNSDistTest):
 
     _config_template = """