]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
feat(dnsdist): Allow setting TTL in DNSQuestion:spoof 17701/head
authorPieter Lexis <pieter.lexis@powerdns.com>
Tue, 7 Jul 2026 13:22:14 +0000 (15:22 +0200)
committerPieter Lexis <pieter.lexis@powerdns.com>
Fri, 10 Jul 2026 11:05:07 +0000 (13:05 +0200)
Closes: #16470
pdns/dnsdistdist/dnsdist-lua-bindings-dnsquestion.cc
pdns/dnsdistdist/docs/reference/dq.rst
regression-tests.dnsdist/test_Spoofing.py

index 599f7247e63381ecc63057424d4ff936f9da237e..a03ec04f4cba791789d9cb65db0c73f5f360273b 100644 (file)
@@ -314,8 +314,9 @@ void setupLuaBindingsDNSQuestion([[maybe_unused]] LuaContext& luaCtx)
     return true;
   });
 
-  luaCtx.registerFunction<void (DNSQuestion::*)(const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>&, std::optional<uint16_t>)>("spoof", [](DNSQuestion& dnsQuestion, const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>& response, std::optional<uint16_t> typeForAny) {
+  luaCtx.registerFunction<void (DNSQuestion::*)(const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>&, std::optional<uint16_t>, std::optional<uint32_t>)>("spoof", [](DNSQuestion& dnsQuestion, const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>& response, std::optional<uint16_t> typeForAny, std::optional<uint32_t> ttl) {
     dnsdist::ResponseConfig responseConfig;
+    responseConfig.ttl = ttl.value_or(60);
     if (response.type() == typeid(LuaArray<ComboAddress>)) {
       std::vector<ComboAddress> data;
       auto responses = boost::get<LuaArray<ComboAddress>>(response);
index a4f30382371b95a76b4f38460173219e8f418f06..469b517fc9404e67423f788715054302a35c20aa 100644 (file)
@@ -373,11 +373,14 @@ This state can be modified from the various hooks.
     :param string tail: The new data
     :returns: true if the operation succeeded, false otherwise
 
-  .. method:: spoof(ip|ips|raw|raws [, typeForAny])
+  .. method:: spoof(ip|ips|raw|raws [, typeForAny[, ttl]])
 
     .. versionchanged:: 1.9.0
       Optional parameter ``typeForAny`` added.
 
+    .. versionchanged:: 2.2.0
+      Optional parameter ``ttl`` added.
+
     Forge a response with the specified record data as raw bytes. If you specify list of raws (it is assumed they match the query type), all will get spoofed in.
 
     :param ComboAddress ip: The `ComboAddress` to be spoofed, e.g. `newCA("192.0.2.1")`.
@@ -385,6 +388,7 @@ This state can be modified from the various hooks.
     :param string raw: The raw string to be spoofed, e.g. `"\\192\\000\\002\\001"`.
     :param table raws: The raw strings to be spoofed, e.g. `{ "\\192\\000\\002\\001", "\\192\\000\\002\\002" }`.
     :param int typeForAny: The type to use for raw responses when the requested type is ``ANY``, as using ``ANY`` for the type of the response record would not make sense.
+    :param int ttl: The TTL to set for the spoofed response, with a default value of 60.
 
   .. method:: suspend(asyncID, queryID, timeoutMS) -> bool
 
index 5446534fe93e29914195e5b13bf501f182a6e43a..88220948ceb1a7fd7fbdb9f18646e05bee119f35 100644 (file)
@@ -399,9 +399,38 @@ class SpoofingTests(object):
             self.checkMessageNoEDNS(expectedResponse, receivedResponse)
             self.assertEqual(receivedResponse.answer[0].ttl, 60)
 
+    def testSpoofLuaTtl(self):
+        """
+        Spoofing: With a TTL
+        """
+        name = "lua-ttl.spoofing.tests.powerdns.com."
+        query = dns.message.make_query(name, "A", "IN")
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags &= ~dns.flags.AA
+        rrset = dns.rrset.from_text(name, 300, dns.rdataclass.IN, dns.rdatatype.A, "192.0.2.1")
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEqual(expectedResponse, receivedResponse)
+            self.assertEqual(receivedResponse.answer[0].ttl, 300)
+
 
 class TestSpoofingViaLuaConfig(DNSDistTest, SpoofingTests):
     _config_template = """
+    function spoofWithTTL(dq)
+        if(dq.qtype==DNSQType.A)
+        then
+          dq:spoof({ newCA("192.0.2.1") }, nil, 300)
+          return DNSAction.HeaderModify
+        else
+          return DNSAction.None
+        end
+    end
+
     addAction(SuffixMatchNodeRule("spoofaction.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}))
     addAction(SuffixMatchNodeRule("spoofaction-aa.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {aa=true}))
     addAction(SuffixMatchNodeRule("spoofaction-ad.spoofing.tests.powerdns.com."), SpoofAction({"192.0.2.1", "2001:DB8::1"}, {ad=true}))
@@ -418,6 +447,7 @@ class TestSpoofingViaLuaConfig(DNSDistTest, SpoofingTests):
     addAction(AndRule{SuffixMatchNodeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction({"\\192\\000\\002\\001", "\\192\\000\\002\\002"}))
     -- rfc8482
     addAction(AndRule{SuffixMatchNodeRule("raw-any.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.ANY)}, SpoofRawAction("\\007rfc\\056\\052\\056\\050\\000", { typeForAny=DNSQType.HINFO }))
+    addAction(SuffixMatchNodeRule("lua-ttl.spoofing.tests.powerdns.com."), LuaAction(spoofWithTTL))
     newServer{address="127.0.0.1:%d"}
     """
 
@@ -626,6 +656,22 @@ query_rules:
         - '\\007rfc\\056\\052\\056\\050\\000'
       vars:
         ttl: 60
+  - selector:
+      type: "QNameSuffix"
+      suffixes:
+        - "lua-ttl.spoofing.tests.powerdns.com."
+    action:
+      type: "Lua"
+      function_code: |
+        return function(dq)
+            if(dq.qtype==DNSQType.A)
+            then
+              dq:spoof({ newCA("192.0.2.1") }, nil, 300)
+              return DNSAction.HeaderModify
+            else
+              return DNSAction.None
+            end
+        end
     """
     _yaml_config_params = ["_testServerPort"]
     _config_params = []