]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add DNSQuestion:changeName() to change the target of a query
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 13 Jan 2023 10:57:41 +0000 (11:57 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 24 Jan 2023 16:32:09 +0000 (17:32 +0100)
pdns/dnsdist-lua-bindings-dnsquestion.cc
pdns/dnsdistdist/docs/reference/dq.rst
regression-tests.dnsdist/test_Advanced.py

index 3d2603261b8dd028a68d17a63e937107dbde87a3..6db675812515a0ae3f4cb30dc9dea7c00c7c18fa 100644 (file)
@@ -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<bool(DNSQuestion::*)(const DNSName& newName)>("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<void(DNSQuestion::*)(const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>& response)>("spoof", [](DNSQuestion& dq, const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>& response) {
       if (response.type() == typeid(LuaArray<ComboAddress>)) {
           std::vector<ComboAddress> data;
@@ -435,6 +444,14 @@ private:
     return dnsdist::suspendResponse(dr, asyncID, queryID, timeoutMs);
   });
 
+  luaCtx.registerFunction<bool(DNSResponse::*)(const DNSName& newName)>("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<bool(DNSResponse::*)()>("restart", [](DNSResponse& dr) {
     if (!dr.ids.d_packet) {
       return false;
index cd8dd221c886dd6e127338b5cb51f59423029511..8074cde4de09ce8def20e22dfd6cc5ab5592d0c3 100644 (file)
@@ -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
index b5409f1a0eb63860d54c0a141d112b46edd51129..184d8bfa4964c6819d6adf62cb8a3511f60ebcd1 100644 (file)
@@ -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)