From: Charles-Henri Bruyand Date: Wed, 6 Oct 2021 09:10:19 +0000 (+0200) Subject: dnsdist: add lua support for SetEDNSOptionAction to set arbitrary EDNS option and... X-Git-Tag: dnsdist-1.7.0-alpha2~24^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=346410cd6b9173c23779de6cb382824f394d9c60;p=thirdparty%2Fpdns.git dnsdist: add lua support for SetEDNSOptionAction to set arbitrary EDNS option and content to the query --- diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index 72e9718913..75410da5ca 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -663,6 +663,7 @@ const std::vector g_consoleKeywords{ { "SetECSOverrideAction", true, "override", "Whether an existing EDNS Client Subnet value should be overridden (true) or not (false). Subsequent rules are processed after this action" }, { "SetECSPrefixLengthAction", true, "v4, v6", "Set the ECS prefix length. Subsequent rules are processed after this action" }, { "SetMacAddrAction", true, "option", "Add the source MAC address to the query as EDNS0 option option. This action is currently only supported on Linux. Subsequent rules are processed after this action" }, + { "SetEDNSOptionAction", true, "option, data", "Add arbitrary EDNS option and data to the query. Subsequent rules are processed after this action" }, { "SetNoRecurseAction", true, "", "strip RD bit from the question, let it go through" }, { "setOutgoingDoHWorkerThreads", true, "n", "Number of outgoing DoH worker threads" }, { "SetProxyProtocolValuesAction", true, "values", "Set the Proxy-Protocol values for this queries to 'values'" }, diff --git a/pdns/dnsdist-lua-actions.cc b/pdns/dnsdist-lua-actions.cc index b6e83949fe..e600cebb55 100644 --- a/pdns/dnsdist-lua-actions.cc +++ b/pdns/dnsdist-lua-actions.cc @@ -939,6 +939,41 @@ private: uint16_t d_code{3}; }; + +class SetEDNSOptionAction : public DNSAction +{ +public: + // this action does not stop the processing + SetEDNSOptionAction(uint16_t code, const std::string& data) : d_code(code), d_data(data) + {} + + DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override + { + if (dq->getHeader()->arcount) { + return Action::None; + } + + std::string optRData; + generateEDNSOption(d_code, d_data, optRData); + + auto& data = dq->getMutableData(); + if (generateOptRR(optRData, data, dq->getMaximumSize(), g_EdnsUDPPayloadSize, 0, false)) { + dq->getHeader()->arcount = htons(1); + } + + return Action::None; + } + + std::string toString() const override + { + return "add EDNS Option (code="+std::to_string(d_code)+")"; + } + +private: + uint16_t d_code; + std::string d_data; +}; + class SetNoRecurseAction : public DNSAction { public: @@ -2033,6 +2068,10 @@ void setupLuaActions(LuaContext& luaCtx) return std::shared_ptr(new SetMacAddrAction(code)); }); + luaCtx.writeFunction("SetEDNSOptionAction", [](int code, const std::string& data) { + return std::shared_ptr(new SetEDNSOptionAction(code, data)); + }); + luaCtx.writeFunction("MacAddrAction", [](int code) { warnlog("access to MacAddrAction is deprecated and will be removed in a future version, please use SetMacAddrAction instead"); return std::shared_ptr(new SetMacAddrAction(code)); diff --git a/pdns/dnsdistdist/docs/rules-actions.rst b/pdns/dnsdistdist/docs/rules-actions.rst index 3493d7f67c..387d8ade12 100644 --- a/pdns/dnsdistdist/docs/rules-actions.rst +++ b/pdns/dnsdistdist/docs/rules-actions.rst @@ -1289,6 +1289,16 @@ The following actions exist. :param int v4: The IPv4 netmask length :param int v6: The IPv6 netmask length +.. function:: SetEDNSOptionAction(option) + + .. versionadded:: 1.7.0 + + Add arbitrary EDNS option and data to the query. + Subsequent rules are processed after this action. + + :param int option: The EDNS option number + :param string data: The EDNS0 option raw content + .. function:: SetMacAddrAction(option) .. versionadded:: 1.6.0 diff --git a/regression-tests.dnsdist/dnsdisttests.py b/regression-tests.dnsdist/dnsdisttests.py index 9ccea5f500..745ef72001 100644 --- a/regression-tests.dnsdist/dnsdisttests.py +++ b/regression-tests.dnsdist/dnsdisttests.py @@ -730,9 +730,19 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase): self.compareOptions(expected.options, received.options) self.assertTrue(hasECS) + def checkMessageEDNS(self, expected, received): + self.assertEqual(expected, received) + self.assertEqual(received.edns, 0) + self.assertEqual(expected.payload, received.payload) + self.assertEqual(len(expected.options), len(received.options)) + self.compareOptions(expected.options, received.options) + def checkQueryEDNSWithECS(self, expected, received, additionalOptions=0): self.checkMessageEDNSWithECS(expected, received, additionalOptions) + def checkQueryEDNS(self, expected, received): + self.checkMessageEDNS(expected, received) + def checkResponseEDNSWithECS(self, expected, received, additionalOptions=0): self.checkMessageEDNSWithECS(expected, received, additionalOptions) diff --git a/regression-tests.dnsdist/test_Advanced.py b/regression-tests.dnsdist/test_Advanced.py index 13120564b5..d9e3f9de4b 100644 --- a/regression-tests.dnsdist/test_Advanced.py +++ b/regression-tests.dnsdist/test_Advanced.py @@ -6,6 +6,7 @@ import string import time import dns import clientsubnetoption +import cookiesoption from dnsdisttests import DNSDistTest class TestAdvancedAllow(DNSDistTest): @@ -2301,3 +2302,38 @@ class TestProtocols(DNSDistTest): receivedQuery.id = query.id self.assertEqual(receivedQuery, query) self.assertEqual(receivedResponse, response) + +class TestAdvancedSetEDNSOptionAction(DNSDistTest): + + _config_template = """ + addAction("setednsoption.advanced.tests.powerdns.com.", SetEDNSOptionAction(10, "deadbeefdeadc0de")) + newServer{address="127.0.0.1:%s"} + """ + + def testAdvancedSetEDNSOption(self): + """ + Advanced: Set EDNS Option + """ + name = 'setednsoption.advanced.tests.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + + eco = cookiesoption.CookiesOption(b'deadbeef', b'deadc0de') + expectedQuery = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=512, options=[eco]) + + response = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 3600, + dns.rdataclass.IN, + dns.rdatatype.A, + '127.0.0.1') + response.answer.append(rrset) + + for method in ("sendUDPQuery", "sendTCPQuery"): + sender = getattr(self, method) + (receivedQuery, receivedResponse) = sender(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = expectedQuery.id + self.assertEqual(expectedQuery, receivedQuery) + self.assertEqual(response, receivedResponse) + self.checkQueryEDNS(expectedQuery, receivedQuery)