From: Sander Hoentjen Date: Fri, 5 Feb 2021 11:07:22 +0000 (+0100) Subject: dnsdist: Add option to spoofRawAction to spoof multiple answers X-Git-Tag: dnsdist-1.6.0-alpha2~48^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=b85487c36a82499e3e58aba4d1c13592f55a537c;p=thirdparty%2Fpdns.git dnsdist: Add option to spoofRawAction to spoof multiple answers --- diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index e4de181594..88e2de511e 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -667,7 +667,7 @@ const std::vector g_consoleKeywords{ { "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"}, { "SpoofAction", true, "ip|list of ips [, options]", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" }, { "SpoofCNAMEAction", true, "cname [, options]", "Forge a response with the specified CNAME value" }, - { "SpoofRawAction", true, "raw [, options]", "Forge a response with the specified record data as raw bytes" }, + { "SpoofRawAction", true, "raw|list of raws [, options]", "Forge a response with the specified record data as raw bytes. If you specify multiple raws (it is assumed they match the query type), all will get spoofed in" }, { "SuffixMatchNodeRule", true, "smn[, quiet]", "Matches based on a group of domain suffixes for rapid testing of membership. Pass true as second parameter to prevent listing of all domains matched" }, { "TagRule", true, "name [, value]", "matches if the tag named 'name' is present, with the given 'value' matching if any" }, { "TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP" }, diff --git a/pdns/dnsdist-lua-actions.cc b/pdns/dnsdist-lua-actions.cc index c488e649fc..a8e1a4d5a9 100644 --- a/pdns/dnsdist-lua-actions.cc +++ b/pdns/dnsdist-lua-actions.cc @@ -540,21 +540,29 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu uint16_t qtype = dq->qtype; // do we even have a response? if (d_cname.empty() && - d_rawResponse.empty() && + d_rawResponses.empty() && d_types.count(qtype) == 0) { return Action::None; } vector addrs; + vector rawResponses; unsigned int totrdatalen = 0; uint16_t numberOfRecords = 0; if (!d_cname.empty()) { qtype = QType::CNAME; totrdatalen += d_cname.getStorage().size(); numberOfRecords = 1; - } else if (!d_rawResponse.empty()) { - totrdatalen += d_rawResponse.size(); - numberOfRecords = 1; + } else if (!d_rawResponses.empty()) { + rawResponses.reserve(d_rawResponses.size()); + for(const auto& rawResponse : d_rawResponses){ + totrdatalen += rawResponse.size(); + rawResponses.push_back(rawResponse); + ++numberOfRecords; + } + if (rawResponses.size() > 1) { + shuffle(rawResponses.begin(), rawResponses.end(), t_randomEngine); + } } else { for(const auto& addr : d_addrs) { @@ -617,16 +625,21 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu memcpy(dest, wireData.c_str(), wireData.length()); dq->getHeader()->ancount++; } - else if (!d_rawResponse.empty()) { - uint16_t rdataLen = htons(d_rawResponse.size()); + else if (!rawResponses.empty()) { qtype = htons(qtype); - memcpy(&recordstart[2], &qtype, sizeof(qtype)); - memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen)); + for(const auto& rawResponse : rawResponses){ + uint16_t rdataLen = htons(rawResponse.size()); + memcpy(&recordstart[2], &qtype, sizeof(qtype)); + memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen)); - memcpy(dest, recordstart, sizeof(recordstart)); - dest += sizeof(recordstart); - memcpy(dest, d_rawResponse.c_str(), d_rawResponse.size()); - dq->getHeader()->ancount++; + memcpy(dest, recordstart, sizeof(recordstart)); + dest += sizeof(recordstart); + + memcpy(dest, rawResponse.c_str(), rawResponse.size()); + dest += rawResponse.size(); + + dq->getHeader()->ancount++; + } raw = true; } else { @@ -1707,12 +1720,13 @@ void setupLuaActions(LuaContext& luaCtx) luaCtx.writeFunction("SpoofAction", [](boost::variant>> inp, boost::optional vars) { vector addrs; - if(auto s = boost::get(&inp)) + if(auto s = boost::get(&inp)) { addrs.push_back(ComboAddress(*s)); - else { + } else { const auto& v = boost::get>>(inp); - for(const auto& a: v) + for(const auto& a: v) { addrs.push_back(ComboAddress(a.second)); + } } auto ret = std::shared_ptr(new SpoofAction(addrs)); @@ -1728,8 +1742,18 @@ void setupLuaActions(LuaContext& luaCtx) return ret; }); - luaCtx.writeFunction("SpoofRawAction", [](const std::string& raw, boost::optional vars) { - auto ret = std::shared_ptr(new SpoofAction(raw)); + luaCtx.writeFunction("SpoofRawAction", [](boost::variant>> inp, boost::optional vars) { + vector raws; + if(auto s = boost::get(&inp)) { + raws.push_back(*s); + } else { + const auto& v = boost::get>>(inp); + for(const auto& raw: v) { + raws.push_back(raw.second); + } + } + + auto ret = std::shared_ptr(new SpoofAction(raws)); auto sa = std::dynamic_pointer_cast(ret); parseResponseConfig(vars, sa->d_responseConfig); return ret; diff --git a/pdns/dnsdist-lua.hh b/pdns/dnsdist-lua.hh index 25d5e10c8e..b40073613a 100644 --- a/pdns/dnsdist-lua.hh +++ b/pdns/dnsdist-lua.hh @@ -55,7 +55,7 @@ public: { } - SpoofAction(const std::string& raw): d_rawResponse(raw) + SpoofAction(const vector& raws): d_rawResponses(raws) { } @@ -67,7 +67,7 @@ public: if (!d_cname.empty()) { ret += d_cname.toString() + " "; } - else if (!d_rawResponse.empty()) { + if (d_rawResponses.size() > 0) { ret += "raw bytes "; } else { @@ -83,7 +83,7 @@ private: static thread_local std::default_random_engine t_randomEngine; std::vector d_addrs; std::set d_types; - std::string d_rawResponse; + std::vector d_rawResponses; DNSName d_cname; }; diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 0856ec847e..d6c6aa9ac5 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -735,7 +735,9 @@ static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent, string result; if (raw) { - SpoofAction sa(spoofContent); + std::vector raws; + stringtok(raws, spoofContent, ","); + SpoofAction sa(raws); sa(&dq, &result); } else { diff --git a/pdns/dnsdistdist/docs/rules-actions.rst b/pdns/dnsdistdist/docs/rules-actions.rst index fa860d6516..6749e45361 100644 --- a/pdns/dnsdistdist/docs/rules-actions.rst +++ b/pdns/dnsdistdist/docs/rules-actions.rst @@ -1373,15 +1373,19 @@ The following actions exist. * ``ttl``: int - The TTL of the record. .. function:: SpoofRawAction(rawAnswer [, options]) + SpoofRawAction(rawAnswers [, options]) .. versionadded:: 1.5.0 + .. versionchanged:: 1.6.0 + Up to 1.6.0, it was only possible to spoof one answer. + Forge a response with the specified raw bytes as record data. .. code-block:: Lua - -- select queries for the 'raw.powerdns.com.' name and TXT type, and answer with a "aaa" "bbb" TXT record: - addAction(AndRule({QNameRule('raw.powerdns.com.'), QTypeRule(DNSQType.TXT)}), SpoofRawAction("\003aaa\004bbbb")) + -- select queries for the 'raw.powerdns.com.' name and TXT type, and answer with both a "aaa" "bbbb" and "ccc" TXT record: + addAction(AndRule({QNameRule('raw.powerdns.com.'), QTypeRule(DNSQType.TXT)}), SpoofRawAction({"\003aaa\004bbbb", "\003ccc"})) -- select queries for the 'raw-srv.powerdns.com.' name and SRV type, and answer with a '0 0 65535 srv.powerdns.com.' SRV record, setting the AA bit to 1 and the TTL to 3600s 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' @@ -1390,6 +1394,7 @@ The following actions exist. :func:`DNSName:toDNSString` is convenient for converting names to wire format for passing to ``SpoofRawAction``. :param string rawAnswer: The raw record data + :param {string} rawAnswers: A table of raw record data to spoof :param table options: A table with key: value pairs with options. Options: diff --git a/regression-tests.dnsdist/test_Spoofing.py b/regression-tests.dnsdist/test_Spoofing.py index ff2675c313..479a4cf876 100644 --- a/regression-tests.dnsdist/test_Spoofing.py +++ b/regression-tests.dnsdist/test_Spoofing.py @@ -16,6 +16,8 @@ class TestSpoofingSpoof(DNSDistTest): addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction("\\192\\000\\002\\001")) addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction("\\003aaa\\004bbbb\\011ccccccccccc")) addAction(AndRule{makeRule("raw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.SRV)}, SpoofRawAction("\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000", { aa=true, ttl=3600 })) + addAction(AndRule{makeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.TXT)}, SpoofRawAction({"\\003aaa\\004bbbb", "\\011ccccccccccc"})) + addAction(AndRule{makeRule("multiraw.spoofing.tests.powerdns.com"), QTypeRule(DNSQType.A)}, SpoofRawAction({"\\192\\000\\002\\001", "\\192\\000\\002\\002"})) newServer{address="127.0.0.1:%s"} """ @@ -357,6 +359,50 @@ class TestSpoofingSpoof(DNSDistTest): self.assertEquals(expectedResponse, receivedResponse) self.assertEquals(receivedResponse.answer[0].ttl, 3600) + def testSpoofRawActionMulti(self): + """ + Spoofing: Spoof a response from several raw bytes + """ + name = 'multiraw.spoofing.tests.powerdns.com.' + + # A + 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, + 60, + dns.rdataclass.IN, + dns.rdatatype.A, + '192.0.2.1', '192.0.2.2') + expectedResponse.answer.append(rrset) + + for method in ("sendUDPQuery", "sendTCPQuery"): + sender = getattr(self, method) + (_, receivedResponse) = sender(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.answer[0].ttl, 60) + + # TXT + query = dns.message.make_query(name, 'TXT', '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.TXT, + '"aaa" "bbbb"', '"ccccccccccc"') + expectedResponse.answer.append(rrset) + + for method in ("sendUDPQuery", "sendTCPQuery"): + sender = getattr(self, method) + (_, receivedResponse) = sender(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.answer[0].ttl, 60) + class TestSpoofingLuaSpoof(DNSDistTest): _config_template = """