From: Samir Aguiar Date: Thu, 5 Jun 2025 15:02:53 +0000 (+0000) Subject: dnsdist: add SetEDNSOptionResponseAction X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F15635%2Fhead;p=thirdparty%2Fpdns.git dnsdist: add SetEDNSOptionResponseAction --- diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index cb53413f3c..55096a34c7 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -797,6 +797,7 @@ const std::vector 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" }, diff --git a/pdns/dnsdistdist/docs/reference/actions.rst b/pdns/dnsdistdist/docs/reference/actions.rst index c0489b6dd2..838c0f9b4d 100644 --- a/pdns/dnsdistdist/docs/reference/actions.rst +++ b/pdns/dnsdistdist/docs/reference/actions.rst @@ -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 diff --git a/regression-tests.dnsdist/test_Responses.py b/regression-tests.dnsdist/test_Responses.py index af16a644bf..13ad28d5e2 100644 --- a/regression-tests.dnsdist/test_Responses.py +++ b/regression-tests.dnsdist/test_Responses.py @@ -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)