Skip to content

Commit

Permalink
dnsdist: Spoof a raw response for ANY queries
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rgacogne committed Dec 5, 2023
1 parent 398e52b commit eeaf281
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 12 deletions.
19 changes: 13 additions & 6 deletions pdns/dnsdist-lua-actions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -2494,16 +2497,20 @@ void setupLuaActions(LuaContext& luaCtx)

luaCtx.writeFunction("SpoofRawAction", [](LuaTypeOrArrayOf<std::string> inp, boost::optional<responseParams_t> vars) {
vector<string> raws;
if(auto s = boost::get<std::string>(&inp)) {
raws.push_back(*s);
if (auto str = boost::get<std::string>(&inp)) {
raws.push_back(*str);
} else {
const auto& v = boost::get<LuaArray<std::string>>(inp);
for(const auto& raw: v) {
const auto& vect = boost::get<LuaArray<std::string>>(inp);
for(const auto& raw: vect) {
raws.push_back(raw.second);
}
}

auto ret = std::shared_ptr<DNSAction>(new SpoofAction(raws));
uint32_t qtypeForAny{0};
getOptionalValue<uint32_t>(vars, "typeForAny", qtypeForAny);
if (qtypeForAny > std::numeric_limits<uint16_t>::max()) {
qtypeForAny = 0;
}
auto ret = std::shared_ptr<DNSAction>(new SpoofAction(raws, qtypeForAny > 0 ? static_cast<uint16_t>(qtypeForAny) : std::optional<uint16_t>()));
auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
parseResponseConfig(vars, sa->d_responseConfig);
checkAllParametersConsumed("SpoofRawAction", vars);
Expand Down
4 changes: 2 additions & 2 deletions pdns/dnsdist-lua-bindings-dnsquestion.cc
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
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) {
luaCtx.registerFunction<void(DNSQuestion::*)(const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>&, boost::optional<uint16_t>)>("spoof", [](DNSQuestion& dq, const boost::variant<LuaArray<ComboAddress>, LuaArray<std::string>>& response, boost::optional<uint16_t> typeForAny) {
if (response.type() == typeid(LuaArray<ComboAddress>)) {
std::vector<ComboAddress> data;
auto responses = boost::get<LuaArray<ComboAddress>>(response);
Expand All @@ -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<uint16_t>());
sa(&dq, &result);
return;
}
Expand Down
3 changes: 2 additions & 1 deletion pdns/dnsdist-lua.hh
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public:
{
}

SpoofAction(const vector<std::string>& raws): d_rawResponses(raws)
SpoofAction(const vector<std::string>& raws, std::optional<uint16_t> typeForAny): d_rawResponses(raws), d_rawTypeForAny(typeForAny)
{
}

Expand Down Expand Up @@ -93,6 +93,7 @@ private:
std::vector<std::string> d_rawResponses;
PacketBuffer d_raw;
DNSName d_cname;
std::optional<uint16_t> d_rawTypeForAny{};
};

class LimitTTLResponseAction : public DNSResponseAction, public boost::noncopyable
Expand Down
2 changes: 1 addition & 1 deletion pdns/dnsdist.cc
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,7 @@ static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent,
if (raw) {
std::vector<std::string> raws;
stringtok(raws, spoofContent, ",");
SpoofAction sa(raws);
SpoofAction sa(raws, std::nullopt);
sa(&dq, &result);
}
else {
Expand Down
2 changes: 1 addition & 1 deletion pdns/dnsdistdist/dnsdist-lua-ffi.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
6 changes: 5 additions & 1 deletion pdns/dnsdistdist/docs/reference/dq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions pdns/dnsdistdist/docs/rules-actions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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``.

Expand All @@ -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])

Expand Down
25 changes: 25 additions & 0 deletions regression-tests.dnsdist/test_Spoofing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
"""

Expand Down Expand Up @@ -385,6 +387,29 @@ def testSpoofRawChaosAction(self):
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):
"""
Expand Down

0 comments on commit eeaf281

Please sign in to comment.