]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: add lua support for SetEDNSOptionAction to set arbitrary EDNS option and...
authorCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Wed, 6 Oct 2021 09:10:19 +0000 (11:10 +0200)
committerCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Wed, 6 Oct 2021 09:24:15 +0000 (11:24 +0200)
pdns/dnsdist-console.cc
pdns/dnsdist-lua-actions.cc
pdns/dnsdistdist/docs/rules-actions.rst
regression-tests.dnsdist/dnsdisttests.py
regression-tests.dnsdist/test_Advanced.py

index 72e97189138edd9c8d242d63cbe9c7b6f6a7d6f6..75410da5ca68155f6c93a945efba67945afd0917 100644 (file)
@@ -663,6 +663,7 @@ const std::vector<ConsoleKeyword> 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'" },
index b6e83949fec1a2292c9df806fdbc7f5ba038f17f..e600cebb55de921d33c02b031f636889fab705af 100644 (file)
@@ -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<DNSAction>(new SetMacAddrAction(code));
     });
 
+  luaCtx.writeFunction("SetEDNSOptionAction", [](int code, const std::string& data) {
+      return std::shared_ptr<DNSAction>(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<DNSAction>(new SetMacAddrAction(code));
index 3493d7f67c79bd5293f763fd1cde471eff607286..387d8ade12cb805ea06a248e11602075bbfd031c 100644 (file)
@@ -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
index 9ccea5f5008234bceb7271b7d69b9e9a03360b3a..745ef72001e49cd89a4123e9a53d1b26b21fc937 100644 (file)
@@ -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)
 
index 13120564b5fa9de7ab79b0ff357b14b677b8a931..d9e3f9de4b05039b3076a0e86b5c79dc7a51ec1b 100644 (file)
@@ -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)