]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add Lua FFI interfaces for changing the qname of a query
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 13 Jan 2023 11:36:49 +0000 (12:36 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 24 Jan 2023 16:33:18 +0000 (17:33 +0100)
pdns/dnsdist-lua-bindings-dnsquestion.cc
pdns/dnsdistdist/dnsdist-lua-ffi-interface.h
pdns/dnsdistdist/dnsdist-lua-ffi.cc
pdns/dnsdistdist/docs/reference/dq.rst
regression-tests.dnsdist/test_Advanced.py

index 6db675812515a0ae3f4cb30dc9dea7c00c7c18fa..a26fb43b65af9ae22d5cc782e22f8e74d2a3e584 100644 (file)
@@ -168,8 +168,8 @@ 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)) {
+  luaCtx.registerFunction<bool(DNSQuestion::*)(const DNSName& newName)>("changeName", [](DNSQuestion& dq, const DNSName& newName) -> bool {
+    if (!dnsdist::changeNameInDNSPacket(dq.getMutableData(), dq.ids.qname, newName)) {
       return false;
     }
     dq.ids.qname = newName;
@@ -444,8 +444,8 @@ 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)) {
+  luaCtx.registerFunction<bool(DNSResponse::*)(const DNSName& newName)>("changeName", [](DNSResponse& dr, const DNSName& newName) -> bool {
+    if (!dnsdist::changeNameInDNSPacket(dr.getMutableData(), dr.ids.qname, newName)) {
       return false;
     }
     dr.ids.qname = newName;
index 01e64005574c18f1692f6b6fd33d5d81acd2a74d..69dab226025e8fc0019fe19e4350c6b23cabee1d 100644 (file)
@@ -152,6 +152,7 @@ void dnsdist_ffi_dnsresponse_limit_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t m
 /* decrease the returned TTL but _after_ inserting the original response into the packet cache */
 void dnsdist_ffi_dnsresponse_set_max_returned_ttl(dnsdist_ffi_dnsresponse_t* dr, uint32_t max) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_dnsresponse_clear_records_type(dnsdist_ffi_dnsresponse_t* dr, uint16_t qtype) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_dnsresponse_rebase(dnsdist_ffi_dnsresponse_t* dr, const char* initialName, size_t initialNameSize) __attribute__ ((visibility ("default")));
 
 bool dnsdist_ffi_dnsquestion_set_async(dnsdist_ffi_dnsquestion_t* dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) __attribute__ ((visibility ("default")));
 bool dnsdist_ffi_dnsresponse_set_async(dnsdist_ffi_dnsquestion_t* dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) __attribute__ ((visibility ("default")));
@@ -160,6 +161,7 @@ bool dnsdist_ffi_resume_from_async(uint16_t asyncID, uint16_t queryID, const cha
 bool dnsdist_ffi_drop_from_async(uint16_t asyncID, uint16_t queryID) __attribute__ ((visibility ("default")));
 bool dnsdist_ffi_set_answer_from_async(uint16_t asyncID, uint16_t queryID, const char* raw, size_t rawSize) __attribute__ ((visibility ("default")));
 bool dnsdist_ffi_set_rcode_from_async(uint16_t asyncID, uint16_t queryID, uint8_t rcode, bool clearAnswers) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_resume_from_async_with_alternate_name(uint16_t asyncID, uint16_t queryID, const char* alternateName, size_t alternateNameSize, const char* tag, size_t tagSize, const char* tagValue, size_t tagValueSize, const char* formerNameTagName, size_t formerNameTagSize) __attribute__ ((visibility ("default")));
 
 typedef struct dnsdist_ffi_proxy_protocol_value {
   const char* value;
@@ -219,6 +221,7 @@ uint16_t dnsdist_ffi_dnspacket_get_record_class(const dnsdist_ffi_dnspacket_t* p
 uint32_t dnsdist_ffi_dnspacket_get_record_ttl(const dnsdist_ffi_dnspacket_t* packet, size_t idx) __attribute__ ((visibility ("default")));
 uint16_t dnsdist_ffi_dnspacket_get_record_content_length(const dnsdist_ffi_dnspacket_t* packet, size_t idx) __attribute__ ((visibility ("default")));
 uint16_t dnsdist_ffi_dnspacket_get_record_content_offset(const dnsdist_ffi_dnspacket_t* packet, size_t idx) __attribute__ ((visibility ("default")));
+size_t dnsdist_ffi_dnspacket_get_name_at_offset_raw(const char* packet, size_t packetSize, size_t offset, char* name, size_t nameSize) __attribute__ ((visibility ("default")));
 void dnsdist_ffi_dnspacket_free(dnsdist_ffi_dnspacket_t*) __attribute__ ((visibility ("default")));
 
 void dnsdist_ffi_metric_inc(const char* metricName, size_t metricNameLen) __attribute__ ((visibility ("default")));
index 28738d0aac8b109cd5dc6256e5a2233d1bd8c718..8da95de022096eaf4bbaa2dd060dae7911bb6a45 100644 (file)
@@ -701,6 +701,31 @@ void dnsdist_ffi_dnsresponse_clear_records_type(dnsdist_ffi_dnsresponse_t* dr, u
   }
 }
 
+bool dnsdist_ffi_dnsresponse_rebase(dnsdist_ffi_dnsresponse_t* dr, const char* initialName, size_t initialNameSize)
+{
+  if (dr == nullptr || dr->dr == nullptr || initialName == nullptr || initialNameSize == 0) {
+    return false;
+  }
+
+  try {
+    DNSName parsed(initialName, initialNameSize, 0, false);
+
+    if (!dnsdist::changeNameInDNSPacket(dr->dr->getMutableData(), dr->dr->ids.qname, parsed)) {
+      return false;
+    }
+
+    // set qname to new one
+    dr->dr->ids.qname = parsed;
+    dr->dr->ids.skipCache = true;
+  }
+  catch (const std::exception& e) {
+    vinfolog("Error rebasing packet on a new DNSName: %s", e.what());
+    return false;
+  }
+
+  return true;
+}
+
 bool dnsdist_ffi_dnsquestion_set_async(dnsdist_ffi_dnsquestion_t* dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)
 {
   try {
@@ -785,6 +810,72 @@ bool dnsdist_ffi_set_rcode_from_async(uint16_t asyncID, uint16_t queryID, uint8_
   return dnsdist::queueQueryResumptionEvent(std::move(query));
 }
 
+bool dnsdist_ffi_resume_from_async_with_alternate_name(uint16_t asyncID, uint16_t queryID, const char* alternateName, size_t alternateNameSize, const char* tag, size_t tagSize, const char* tagValue, size_t tagValueSize, const char* formerNameTagName, size_t formerNameTagSize)
+{
+  if (!dnsdist::g_asyncHolder) {
+    return false;
+  }
+
+  auto query = dnsdist::g_asyncHolder->get(asyncID, queryID);
+  if (!query) {
+    vinfolog("Unable to resume with an alternate name, no object found for asynchronous ID %d and query ID %d", asyncID, queryID);
+    return false;
+  }
+
+  auto& ids = query->query.d_idstate;
+  DNSName originalName = ids.qname;
+
+  try {
+    DNSName parsed(alternateName, alternateNameSize, 0, false);
+
+    PacketBuffer initialPacket;
+    if (query->d_isResponse) {
+      if (!ids.d_packet) {
+        return false;
+      }
+      initialPacket = std::move(*ids.d_packet);
+    }
+    else {
+      initialPacket = std::move(query->query.d_buffer);
+    }
+
+    // edit qname in query packet
+    if (!dnsdist::changeNameInDNSPacket(initialPacket, originalName, parsed)) {
+      return false;
+    }
+    if (query->d_isResponse) {
+      query->d_isResponse = false;
+    }
+    query->query.d_buffer = std::move(initialPacket);
+    // set qname to new one
+    ids.qname = std::move(parsed);
+  }
+  catch (const std::exception& e) {
+    vinfolog("Error rebasing packet on a new DNSName: %s", e.what());
+    return false;
+  }
+
+  // save existing qname in tag
+  if (formerNameTagName != nullptr && formerNameTagSize > 0) {
+    if (!ids.qTag) {
+      ids.qTag = std::make_unique<QTag>();
+    }
+    (*ids.qTag)[std::string(formerNameTagName, formerNameTagSize)] = originalName.getStorage();
+  }
+
+  if (tag != nullptr && tagSize > 0) {
+    if (!ids.qTag) {
+      ids.qTag = std::make_unique<QTag>();
+    }
+    (*ids.qTag)[std::string(tag, tagSize)] = std::string(tagValue, tagValueSize);
+  }
+
+  ids.skipCache = true;
+
+  // resume as query
+  return dnsdist::queueQueryResumptionEvent(std::move(query));
+}
+
 bool dnsdist_ffi_drop_from_async(uint16_t asyncID, uint16_t queryID)
 {
   if (!dnsdist::g_asyncHolder) {
@@ -1450,6 +1541,26 @@ uint16_t dnsdist_ffi_dnspacket_get_record_content_offset(const dnsdist_ffi_dnspa
   return packet->overlay.d_records.at(idx).d_contentOffset;
 }
 
+size_t dnsdist_ffi_dnspacket_get_name_at_offset_raw(const char* packet, size_t packetSize, size_t offset, char* name, size_t nameSize)
+{
+  if (packet == nullptr || name == nullptr || nameSize == 0 || offset >= packetSize) {
+    return 0;
+  }
+  try {
+    DNSName parsed(packet, packetSize, offset, true);
+    const auto& storage = parsed.getStorage();
+    if (nameSize < storage.size()) {
+      return 0;
+    }
+    memcpy(name, storage.data(), storage.size());
+    return storage.size();
+  }
+  catch (const std::exception& e) {
+    vinfolog("Error parsing DNSName via dnsdist_ffi_dnspacket_get_name_at_offset_raw: %s", e.what());
+  }
+  return 0;
+}
+
 void dnsdist_ffi_dnspacket_free(dnsdist_ffi_dnspacket_t* packet)
 {
   if (packet != nullptr) {
index 8074cde4de09ce8def20e22dfd6cc5ab5592d0c3..7a87c6aeb5f55f3be64b0982f52b26fccabe9a33 100644 (file)
@@ -217,13 +217,13 @@ This state can be modified from the various hooks.
 
     :returns: The trailing data as a null-safe string
 
-  .. method:: DNSQuestion:rebase(newName) -> bool
+  .. method:: DNSQuestion:changeName(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`.
+    See :func:`DNSResponse:changeName`.
     Returns false on failure, true on success.
 
     :param DNSName newName: The new qname to use
@@ -405,11 +405,11 @@ DNSResponse object
 
     :param string func: The function to call to edit TTLs.
 
-  .. method:: DNSResponse:rebase(initialName) -> bool
+  .. method:: DNSResponse:changeName(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.
+    Change, in the DNS payload of the current response, the qname and the owner name of records to the supplied new name, if they are matching exactly the initial qname.
     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
@@ -418,7 +418,7 @@ DNSResponse object
     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`.
+    See also :func:`DNSQuestion:changeName`.
     Returns false on failure, true on success.
 
     :param DNSName initialName: The initial qname
index 184d8bfa4964c6819d6adf62cb8a3511f60ebcd1..643a25c3aa56e20addf265305a13ccc4c48c90ba 100644 (file)
@@ -590,55 +590,55 @@ class TestDNSQuestionTime(DNSDistTest):
             self.assertEqual(receivedQuery, query)
             self.assertEqual(receivedResponse, response)
 
-class TestRebase(DNSDistTest):
+class TestChangeName(DNSDistTest):
     _config_template = """
     local tagName = 'initial-name'
-    function luarebasequery(dq)
+    function luaChangeNamequery(dq)
       dq:setTag(tagName, dq.qname:toString())
-      if not dq:rebase(newDNSName('rebase.advanced.tests.dnsdist.org')) then
+      if not dq:changeName(newDNSName('changeName.advanced.tests.dnsdist.org')) then
         errlog('Error rebasing the query')
         return DNSAction.Drop
       end
       return DNSAction.None
     end
 
-    function luarebaseresponse(dr)
+    function luaChangeNameresponse(dr)
       local initialName = dr:getTag(tagName)
-      if not dr:rebase(newDNSName(initialName)) then
+      if not dr:changeName(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))
+    addAction('changeName.advanced.tests.powerdns.com', LuaAction(luaChangeNamequery))
+    addResponseAction('changeName.advanced.tests.dnsdist.org', LuaResponseAction(luaChangeNameresponse))
     newServer{address="127.0.0.1:%s"}
     """
 
-    def testRebase(self):
+    def testChangeName(self):
         """
-        Advanced: Rebase the query name
+        Advanced: ChangeName the query name
         """
-        name = 'rebase.advanced.tests.powerdns.com.'
+        name = 'changeName.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
+        changedName = 'changeName.advanced.tests.dnsdist.org.'
+        changedQuery = dns.message.make_query(changedName, 'A', 'IN')
+        changedQuery.id = query.id
 
-        response = dns.message.make_response(rebasedQuery)
-        rrset = dns.rrset.from_text(rebasedName,
+        response = dns.message.make_response(changedQuery)
+        rrset = dns.rrset.from_text(changedName,
                                     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.',
+        rrset = dns.rrset.from_text('sub.sub2.changeName.advanced.tests.dnsdist.org.',
                                     60,
                                     dns.rdataclass.IN,
                                     dns.rdatatype.TXT,
-                                    'This text contains sub.sub2.rebase.advanced.tests.dnsdist.org.')
+                                    'This text contains sub.sub2.changeName.advanced.tests.dnsdist.org.')
         response.additional.append(rrset)
 
         expectedResponse = dns.message.make_response(query)
@@ -649,16 +649,16 @@ class TestRebase(DNSDistTest):
                                     '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.',
+        rrset = dns.rrset.from_text('sub.sub2.changeName.advanced.tests.dnsdist.org.',
                                     60,
                                     dns.rdataclass.IN,
                                     dns.rdatatype.TXT,
-                                    'This text contains sub.sub2.rebase.advanced.tests.dnsdist.org.')
+                                    'This text contains sub.sub2.changeName.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(receivedQuery, changedQuery)
             self.assertEqual(receivedResponse, expectedResponse)