From: Charles-Henri Bruyand Date: Wed, 1 Dec 2021 13:57:46 +0000 (+0100) Subject: dnsdist: add lua support to limit reponse TTL values X-Git-Tag: auth-4.7.0-alpha1~123^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=aa8886da10f94f61dccec18d85c531a5787155f7;p=thirdparty%2Fpdns.git dnsdist: add lua support to limit reponse TTL values --- diff --git a/pdns/dnsdist-lua-actions.cc b/pdns/dnsdist-lua-actions.cc index 03475f79b9..ec1450d698 100644 --- a/pdns/dnsdist-lua-actions.cc +++ b/pdns/dnsdist-lua-actions.cc @@ -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(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(new LimitTTLResponseAction(min, max)); + }); + + luaCtx.writeFunction("SetMinTTLResponseAction", [](uint32_t min) { + return std::shared_ptr(new LimitTTLResponseAction(min)); + }); + + luaCtx.writeFunction("SetMaxTTLResponseAction", [](uint32_t max) { + return std::shared_ptr(new LimitTTLResponseAction(0, max)); + }); + luaCtx.writeFunction("RCodeAction", [](uint8_t rcode, boost::optional vars) { auto ret = std::shared_ptr(new RCodeAction(rcode)); auto rca = std::dynamic_pointer_cast(ret); diff --git a/pdns/dnsdist-lua.hh b/pdns/dnsdist-lua.hh index b8230ebad6..0e630eb2cf 100644 --- a/pdns/dnsdist-lua.hh +++ b/pdns/dnsdist-lua.hh @@ -21,6 +21,7 @@ */ #pragma once +#include "dnsparser.hh" #include 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::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(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::max()}; +}; + typedef boost::variant>, std::shared_ptr, DNSName, vector > > luadnsrule_t; std::shared_ptr makeRule(const luadnsrule_t& var); typedef std::unordered_map > luaruleparams_t; diff --git a/pdns/dnsdistdist/dnsdist-lua-ffi-interface.h b/pdns/dnsdistdist/dnsdist-lua-ffi-interface.h index e371d9e1db..021de37d9a 100644 --- a/pdns/dnsdistdist/dnsdist-lua-ffi-interface.h +++ b/pdns/dnsdistdist/dnsdist-lua-ffi-interface.h @@ -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"))); diff --git a/pdns/dnsdistdist/dnsdist-lua-ffi.cc b/pdns/dnsdistdist/dnsdist-lua-ffi.cc index 25e36c218f..ac6676c586 100644 --- a/pdns/dnsdistdist/dnsdist-lua-ffi.cc +++ b/pdns/dnsdistdist/dnsdist-lua-ffi.cc @@ -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::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 = diff --git a/pdns/dnsdistdist/dnsdist-lua-ffi.hh b/pdns/dnsdistdist/dnsdist-lua-ffi.hh index 1030659500..91877bbb4f 100644 --- a/pdns/dnsdistdist/dnsdist-lua-ffi.hh +++ b/pdns/dnsdistdist/dnsdist-lua-ffi.hh @@ -59,6 +59,15 @@ struct dnsdist_ffi_dnsquestion_t boost::optional 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 { diff --git a/pdns/dnsdistdist/docs/rules-actions.rst b/pdns/dnsdistdist/docs/rules-actions.rst index fe322f52ff..d82e2c05b6 100644 --- a/pdns/dnsdistdist/docs/rules-actions.rst +++ b/pdns/dnsdistdist/docs/rules-actions.rst @@ -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 diff --git a/regression-tests.dnsdist/test_Responses.py b/regression-tests.dnsdist/test_Responses.py index 0edad08b2c..b887ab661f 100644 --- a/regression-tests.dnsdist/test_Responses.py +++ b/regression-tests.dnsdist/test_Responses.py @@ -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 = """