From: Remi Gacogne Date: Fri, 13 Jan 2023 10:57:41 +0000 (+0100) Subject: dnsdist: Add DNSQuestion:changeName() to change the target of a query X-Git-Tag: dnsdist-1.8.0-rc1~67^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2079019121332cd04de19ad10b4283b2b51aaa10;p=thirdparty%2Fpdns.git dnsdist: Add DNSQuestion:changeName() to change the target of a query --- diff --git a/pdns/dnsdist-lua-bindings-dnsquestion.cc b/pdns/dnsdist-lua-bindings-dnsquestion.cc index 3d2603261b..6db6758125 100644 --- a/pdns/dnsdist-lua-bindings-dnsquestion.cc +++ b/pdns/dnsdist-lua-bindings-dnsquestion.cc @@ -21,6 +21,7 @@ */ #include "dnsdist.hh" #include "dnsdist-async.hh" +#include "dnsdist-dnsparser.hh" #include "dnsdist-ecs.hh" #include "dnsdist-internal-queries.hh" #include "dnsdist-lua.hh" @@ -167,6 +168,14 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx) return result; }); + luaCtx.registerFunction("rebase", [](DNSQuestion& dq, const DNSName& newName) -> bool { + if (!dnsdist::rebaseDNSPacket(dq.getMutableData(), dq.ids.qname, newName)) { + return false; + } + dq.ids.qname = newName; + return true; + }); + luaCtx.registerFunction, LuaArray>& response)>("spoof", [](DNSQuestion& dq, const boost::variant, LuaArray>& response) { if (response.type() == typeid(LuaArray)) { std::vector data; @@ -435,6 +444,14 @@ private: return dnsdist::suspendResponse(dr, asyncID, queryID, timeoutMs); }); + luaCtx.registerFunction("rebase", [](DNSResponse& dr, const DNSName& newName) -> bool { + if (!dnsdist::rebaseDNSPacket(dr.getMutableData(), dr.ids.qname, newName)) { + return false; + } + dr.ids.qname = newName; + return true; + }); + luaCtx.registerFunction("restart", [](DNSResponse& dr) { if (!dr.ids.d_packet) { return false; diff --git a/pdns/dnsdistdist/docs/reference/dq.rst b/pdns/dnsdistdist/docs/reference/dq.rst index cd8dd221c8..8074cde4de 100644 --- a/pdns/dnsdistdist/docs/reference/dq.rst +++ b/pdns/dnsdistdist/docs/reference/dq.rst @@ -217,6 +217,17 @@ This state can be modified from the various hooks. :returns: The trailing data as a null-safe string + .. method:: DNSQuestion:rebase(newName) -> bool + + .. versionadded:: 1.8.0 + + Change the qname of the current query in the DNS payload. + The reverse operation will have to be done on the response to set it back to the initial name, or the client will be confused and likely drop the response. + See :func:`DNSResponse:rebase`. + Returns false on failure, true on success. + + :param DNSName newName: The new qname to use + .. method:: DNSQuestion:sendTrap(reason) Send an SNMP trap. @@ -394,6 +405,24 @@ DNSResponse object :param string func: The function to call to edit TTLs. + .. method:: DNSResponse:rebase(initialName) -> bool + + .. versionadded:: 1.8.0 + + Change the qname and records matching exactly this qname in the DNS payload of the current response to the supplied new name. + This only makes if the reverse operation was performed on the query, or the client will be confused and likely drop the response. + Note that only records whose owner name matches the qname in the initial response will be rewritten, and that only the owner name itself will be altered, + not the content of the record rdata. For some records this might cause an issue with compression pointers contained in the payload, as they might + no longer point to the correct position in the DNS payload. To prevent that, the records are checked against a list of supported record types, + and the rewriting will not be performed if an unsupported type is present. As of 1.8.0 the list of supported types is: + A, AAAA, DHCID, TXT, OPT, HINFO, DNSKEY, CDNSKEY, DS, CDS, DLV, SSHFP, KEY, CERT, TLSA, SMIMEA, OPENPGPKEY, NSEC, NSEC3, CSYNC, NSEC3PARAM, LOC, NID, L32, L64, EUI48, EUI64, URI, CAA, NS, PTR, CNAME, DNAME, RRSIG, MX, SOA, SRV + Therefore this functionality only makes sense when the initial query is requesting a very simple type, like A or AAAA. + + See also :func:`DNSQuestion:rebase`. + Returns false on failure, true on success. + + :param DNSName initialName: The initial qname + .. method:: DNSResponse:restart() .. versionadded:: 1.8.0 diff --git a/regression-tests.dnsdist/test_Advanced.py b/regression-tests.dnsdist/test_Advanced.py index b5409f1a0e..184d8bfa49 100644 --- a/regression-tests.dnsdist/test_Advanced.py +++ b/regression-tests.dnsdist/test_Advanced.py @@ -589,3 +589,76 @@ class TestDNSQuestionTime(DNSDistTest): receivedQuery.id = query.id self.assertEqual(receivedQuery, query) self.assertEqual(receivedResponse, response) + +class TestRebase(DNSDistTest): + _config_template = """ + local tagName = 'initial-name' + function luarebasequery(dq) + dq:setTag(tagName, dq.qname:toString()) + if not dq:rebase(newDNSName('rebase.advanced.tests.dnsdist.org')) then + errlog('Error rebasing the query') + return DNSAction.Drop + end + return DNSAction.None + end + + function luarebaseresponse(dr) + local initialName = dr:getTag(tagName) + if not dr:rebase(newDNSName(initialName)) then + errlog('Error rebasing the response') + return DNSAction.Drop + end + return DNSAction.None + end + + addAction('rebase.advanced.tests.powerdns.com', LuaAction(luarebasequery)) + addResponseAction('rebase.advanced.tests.dnsdist.org', LuaResponseAction(luarebaseresponse)) + newServer{address="127.0.0.1:%s"} + """ + + def testRebase(self): + """ + Advanced: Rebase the query name + """ + name = 'rebase.advanced.tests.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + + rebasedName = 'rebase.advanced.tests.dnsdist.org.' + rebasedQuery = dns.message.make_query(rebasedName, 'A', 'IN') + rebasedQuery.id = query.id + + response = dns.message.make_response(rebasedQuery) + rrset = dns.rrset.from_text(rebasedName, + 60, + dns.rdataclass.IN, + dns.rdatatype.A, + '4.3.2.1') + response.answer.append(rrset) + rrset = dns.rrset.from_text('sub.sub2.rebase.advanced.tests.dnsdist.org.', + 60, + dns.rdataclass.IN, + dns.rdatatype.TXT, + 'This text contains sub.sub2.rebase.advanced.tests.dnsdist.org.') + response.additional.append(rrset) + + expectedResponse = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 60, + dns.rdataclass.IN, + dns.rdatatype.A, + '4.3.2.1') + expectedResponse.answer.append(rrset) + # we only rewrite records if the owner name matches the new target, nothing else + rrset = dns.rrset.from_text('sub.sub2.rebase.advanced.tests.dnsdist.org.', + 60, + dns.rdataclass.IN, + dns.rdatatype.TXT, + 'This text contains sub.sub2.rebase.advanced.tests.dnsdist.org.') + expectedResponse.additional.append(rrset) + + for method in ("sendUDPQuery", "sendTCPQuery"): + sender = getattr(self, method) + (receivedQuery, receivedResponse) = sender(query, response) + receivedQuery.id = query.id + self.assertEqual(receivedQuery, rebasedQuery) + self.assertEqual(receivedResponse, expectedResponse)