From: Remi Gacogne Date: Mon, 4 Dec 2023 16:09:52 +0000 (+0100) Subject: dnsdist: Spoof a raw response for ANY queries X-Git-Tag: dnsdist-1.9.0-alpha4~9^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=eeaf28195e65041bfd146868265a08e199c30e74;p=thirdparty%2Fpdns.git dnsdist: Spoof a raw response for ANY queries This PR adds the ability to spoof a raw response for ``ANY`` queries, as it would not make sense to use ``ANY`` for the type of the response record. --- diff --git a/pdns/dnsdist-lua-actions.cc b/pdns/dnsdist-lua-actions.cc index dabe0b8dac..736d9aef86 100644 --- a/pdns/dnsdist-lua-actions.cc +++ b/pdns/dnsdist-lua-actions.cc @@ -943,6 +943,9 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu }); } else if (!rawResponses.empty()) { + if (qtype == QType::ANY && d_rawTypeForAny) { + qtype = *d_rawTypeForAny; + } qtype = htons(qtype); for(const auto& rawResponse : rawResponses){ uint16_t rdataLen = htons(rawResponse.size()); @@ -2494,16 +2497,20 @@ void setupLuaActions(LuaContext& luaCtx) luaCtx.writeFunction("SpoofRawAction", [](LuaTypeOrArrayOf inp, boost::optional vars) { vector raws; - if(auto s = boost::get(&inp)) { - raws.push_back(*s); + if (auto str = boost::get(&inp)) { + raws.push_back(*str); } else { - const auto& v = boost::get>(inp); - for(const auto& raw: v) { + const auto& vect = boost::get>(inp); + for(const auto& raw: vect) { raws.push_back(raw.second); } } - - auto ret = std::shared_ptr(new SpoofAction(raws)); + uint32_t qtypeForAny{0}; + getOptionalValue(vars, "typeForAny", qtypeForAny); + if (qtypeForAny > std::numeric_limits::max()) { + qtypeForAny = 0; + } + auto ret = std::shared_ptr(new SpoofAction(raws, qtypeForAny > 0 ? static_cast(qtypeForAny) : std::optional())); auto sa = std::dynamic_pointer_cast(ret); parseResponseConfig(vars, sa->d_responseConfig); checkAllParametersConsumed("SpoofRawAction", vars); diff --git a/pdns/dnsdist-lua-bindings-dnsquestion.cc b/pdns/dnsdist-lua-bindings-dnsquestion.cc index 933d1720b9..377f463062 100644 --- a/pdns/dnsdist-lua-bindings-dnsquestion.cc +++ b/pdns/dnsdist-lua-bindings-dnsquestion.cc @@ -227,7 +227,7 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx) return true; }); - luaCtx.registerFunction, LuaArray>& response)>("spoof", [](DNSQuestion& dq, const boost::variant, LuaArray>& response) { + luaCtx.registerFunction, LuaArray>&, boost::optional)>("spoof", [](DNSQuestion& dq, const boost::variant, LuaArray>& response, boost::optional typeForAny) { if (response.type() == typeid(LuaArray)) { std::vector data; auto responses = boost::get>(response); @@ -248,7 +248,7 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx) data.push_back(resp.second); } std::string result; - SpoofAction sa(data); + SpoofAction sa(data, typeForAny ? *typeForAny : std::optional()); sa(&dq, &result); return; } diff --git a/pdns/dnsdist-lua.hh b/pdns/dnsdist-lua.hh index 7dfe0b30b9..0037d6b9f0 100644 --- a/pdns/dnsdist-lua.hh +++ b/pdns/dnsdist-lua.hh @@ -62,7 +62,7 @@ public: { } - SpoofAction(const vector& raws): d_rawResponses(raws) + SpoofAction(const vector& raws, std::optional typeForAny): d_rawResponses(raws), d_rawTypeForAny(typeForAny) { } @@ -93,6 +93,7 @@ private: std::vector d_rawResponses; PacketBuffer d_raw; DNSName d_cname; + std::optional d_rawTypeForAny{}; }; class LimitTTLResponseAction : public DNSResponseAction, public boost::noncopyable diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index bfaa81bd9f..205afff953 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -863,7 +863,7 @@ static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent, if (raw) { std::vector raws; stringtok(raws, spoofContent, ","); - SpoofAction sa(raws); + SpoofAction sa(raws, std::nullopt); sa(&dq, &result); } else { diff --git a/pdns/dnsdistdist/dnsdist-lua-ffi.cc b/pdns/dnsdistdist/dnsdist-lua-ffi.cc index 2633d64644..98e373159a 100644 --- a/pdns/dnsdistdist/dnsdist-lua-ffi.cc +++ b/pdns/dnsdistdist/dnsdist-lua-ffi.cc @@ -602,7 +602,7 @@ void dnsdist_ffi_dnsquestion_spoof_raw(dnsdist_ffi_dnsquestion_t* dq, const dnsd } std::string result; - SpoofAction sa(data); + SpoofAction sa(data, std::nullopt); sa(dq->dq, &result); } diff --git a/pdns/dnsdistdist/docs/reference/dq.rst b/pdns/dnsdistdist/docs/reference/dq.rst index 4aa675fb1e..634ff81d3f 100644 --- a/pdns/dnsdistdist/docs/reference/dq.rst +++ b/pdns/dnsdistdist/docs/reference/dq.rst @@ -362,16 +362,20 @@ This state can be modified from the various hooks. :param string tail: The new data :returns: true if the operation succeeded, false otherwise - .. method:: DNSQuestion:spoof(ip|ips|raw|raws) + .. method:: DNSQuestion:spoof(ip|ips|raw|raws [, typeForAny]) .. versionadded:: 1.6.0 + .. versionchanged:: 1.9.0 + Optional parameter ``typeForAny`` 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")`. :param table ComboAddresses ips: The `ComboAddress`es to be spoofed, e.g. `{ newCA("192.0.2.1"), newCA("192.0.2.2") }`. :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. .. method:: DNSQuestion:suspend(asyncID, queryID, timeoutMS) -> bool diff --git a/pdns/dnsdistdist/docs/rules-actions.rst b/pdns/dnsdistdist/docs/rules-actions.rst index b4266f05aa..f02583f73c 100644 --- a/pdns/dnsdistdist/docs/rules-actions.rst +++ b/pdns/dnsdistdist/docs/rules-actions.rst @@ -1792,6 +1792,9 @@ The following actions exist. .. versionchanged:: 1.6.0 Up to 1.6.0, it was only possible to spoof one answer. + .. versionchanged:: 1.9.0 + Added the optional parameter ``typeForAny``. + Forge a response with the specified raw bytes as record data. .. code-block:: Lua @@ -1802,6 +1805,8 @@ The following actions exist. addAction(AndRule({QNameRule('raw-srv.powerdns.com.'), QTypeRule(DNSQType.SRV)}), SpoofRawAction("\000\000\000\000\255\255\003srv\008powerdns\003com\000", { aa=true, ttl=3600 })) -- select reverse queries for '127.0.0.1' and answer with 'localhost' addAction(AndRule({QNameRule('1.0.0.127.in-addr.arpa.'), QTypeRule(DNSQType.PTR)}), SpoofRawAction("\009localhost\000")) + -- rfc8482: Providing Minimal-Sized Responses to DNS Queries That Have QTYPE=ANY via HINFO of value "rfc8482" + addAction(QTypeRule(DNSQType.ANY), SpoofRawAction("\007rfc\056\052\056\050\000", { typeForAny=DNSQType.HINFO })) :func:`DNSName:toDNSString` is convenient for converting names to wire format for passing to ``SpoofRawAction``. @@ -1828,6 +1833,7 @@ The following actions exist. * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it. * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query. * ``ttl``: int - The TTL of the record. + * ``typeForAny``: int - The record type to use when responding to queries of type ``ANY``, as using ``ANY`` for the type of the response record would not make sense. .. function:: SpoofSVCAction(svcParams [, options]) diff --git a/regression-tests.dnsdist/test_Spoofing.py b/regression-tests.dnsdist/test_Spoofing.py index 85478a788c..fafc94e100 100644 --- a/regression-tests.dnsdist/test_Spoofing.py +++ b/regression-tests.dnsdist/test_Spoofing.py @@ -19,6 +19,8 @@ class TestSpoofingSpoof(DNSDistTest): addAction(AndRule{SuffixMatchNodeRule("rawchaos.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT), QClassRule(DNSClass.CHAOS)}, SpoofRawAction("\\005chaos")) addAction(AndRule{SuffixMatchNodeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction({"\\003aaa\\004bbbb", "\\011ccccccccccc"})) 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 })) newServer{address="127.0.0.1:%s"} """ @@ -385,6 +387,29 @@ class TestSpoofingSpoof(DNSDistTest): self.assertEqual(expectedResponse, receivedResponse) self.assertEqual(receivedResponse.answer[0].ttl, 60) + def testSpoofRawANYAction(self): + """ + Spoofing: Spoof a HINFO response for ANY queries + """ + name = 'raw-any.spoofing.tests.powerdns.com.' + + query = dns.message.make_query(name, 'ANY', 'IN') + query.flags &= ~dns.flags.RD + expectedResponse = dns.message.make_response(query) + expectedResponse.flags &= ~dns.flags.AA + rrset = dns.rrset.from_text(name, + 60, + dns.rdataclass.IN, + dns.rdatatype.HINFO, + '"rfc8482" ""') + 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, 60) def testSpoofRawActionMulti(self): """