]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: add SetEDNSOptionResponseAction 15635/head
authorSamir Aguiar <sjorgedeaguiar@netskope.com>
Thu, 5 Jun 2025 15:02:53 +0000 (15:02 +0000)
committerSamir Aguiar <sjorgedeaguiar@netskope.com>
Thu, 5 Jun 2025 15:28:33 +0000 (15:28 +0000)
pdns/dnsdist-console.cc
pdns/dnsdistdist/docs/reference/actions.rst
regression-tests.dnsdist/test_Responses.py

index cb53413f3ca980371c7db4aa33930523513e40b2..55096a34c7939ef89b875cb40cb547f309567e30 100644 (file)
@@ -797,6 +797,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "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" },
+  { "SetEDNSOptionResponseAction", true, "option, data", "Add arbitrary EDNS option and data to the response. Subsequent rules are processed after this action"},
   { "SetExtendedDNSErrorAction", true, "infoCode [, extraText]", "Set an Extended DNS Error status that will be added to the response corresponding to the current query. Subsequent rules are processed after this action" },
   { "SetExtendedDNSErrorResponseAction", true, "infoCode [, extraText]", "Set an Extended DNS Error status that will be added to this response. Subsequent rules are processed after this action" },
   { "SetNoRecurseAction", true, "", "strip RD bit from the question, let it go through" },
index c0489b6dd2f942d18ee4d758c6d07efcaf109ade..838c0f9b4dd23f4fbbe56257726c4d43a25af07d 100644 (file)
@@ -586,6 +586,16 @@ The following actions exist.
   :param int option: The EDNS option number
   :param string data: The EDNS0 option raw content
 
+.. function:: SetEDNSOptionResponseAction(option)
+
+  .. versionadded:: 1.9.11
+
+  Add arbitrary EDNS option and data to the response. Any existing EDNS content with the same option code will be replaced.
+  Subsequent rules are processed after this action.
+
+  :param int option: The EDNS option number
+  :param string data: The EDNS0 option raw content
+
 .. function:: SetExtendedDNSErrorAction(infoCode [, extraText])
 
   .. versionadded:: 1.9.0
index af16a644bf3bc4527632eb696dd0be4a18d63be0..13ad28d5e2b275fc2f9836959ec71f037c745b63 100644 (file)
@@ -2,6 +2,7 @@
 from datetime import datetime, timedelta
 import time
 import dns
+import cookiesoption
 from dnsdisttests import DNSDistTest
 
 class TestResponseRuleNXDelayed(DNSDistTest):
@@ -505,3 +506,97 @@ class TestResponseClearRecordsType(DNSDistTest):
             receivedQuery.id = query.id
             self.assertEqual(query, receivedQuery)
             self.assertEqual(expectedResponse, receivedResponse)
+
+class TestAdvancedSetEDNSOptionResponseAction(DNSDistTest):
+    _config_template = """
+    addResponseAction(AllRule(), SetEDNSOptionResponseAction(10, "deadbeefdeadc0de"))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testAdvancedSetEDNSOptionResponse(self):
+        """
+        Responses: Set EDNS Option in response
+        """
+        name = 'setednsoptionresponse.responses.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        response = dns.message.make_response(query)
+        response.use_edns(edns=True, payload=512)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        response.answer.append(rrset)
+
+        eco = cookiesoption.CookiesOption(b'deadbeef', b'deadc0de')
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.use_edns(edns=True, payload=512, options=[eco])
+        expectedResponse.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 = query.id
+            self.assertEqual(query, receivedQuery)
+            self.checkResponseEDNSWithoutECS(expectedResponse, receivedResponse, 1)
+
+    def testAdvancedSetEDNSOptionResponseOverwrite(self):
+        """
+        Responses: Set EDNS Option in response replaces existing option
+        """
+        name = 'setednsoptionresponse-overwrite.responses.tests.powerdns.com.'
+        initialECO = cookiesoption.CookiesOption(b'aaaaaaaa', b'bbbbbbbb')
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        response = dns.message.make_response(query)
+        response.use_edns(edns=True, payload=512, options=[initialECO])
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        response.answer.append(rrset)
+
+        replacementECO = cookiesoption.CookiesOption(b'deadbeef', b'deadc0de')
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.use_edns(edns=True, payload=512, options=[replacementECO])
+        expectedResponse.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 = query.id
+            self.assertEqual(query, receivedQuery)
+            self.checkResponseEDNSWithoutECS(expectedResponse, receivedResponse, 1)
+
+    def testAdvancedSetEDNSOptionResponseWithDOSet(self):
+        """
+        Responses: Set EDNS Option in response (DO bit set)
+        """
+        name = 'setednsoptionresponse-do.responses.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True, want_dnssec=True, payload=4096)
+        response = dns.message.make_response(query)
+        response.use_edns(edns=True, payload=1024)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        response.answer.append(rrset)
+
+        eco = cookiesoption.CookiesOption(b'deadbeef', b'deadc0de')
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.use_edns(edns=True, payload=1024, options=[eco])
+        expectedResponse.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 = query.id
+            self.assertEqual(query, receivedQuery)
+            self.checkResponseEDNSWithoutECS(expectedResponse, receivedResponse, 1)