From: Remi Gacogne Date: Thu, 14 Nov 2019 14:51:23 +0000 (+0100) Subject: dnsdist: Add spoofRawAction() to craft answers from raw bytes X-Git-Tag: auth-4.3.0-beta2~38^2 X-Git-Url: http://git.ipfire.org/?p=thirdparty%2Fpdns.git;a=commitdiff_plain;h=202c4ab98050e2f236c78def5d3b4f0da815351f dnsdist: Add spoofRawAction() to craft answers from raw bytes --- diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index 6c0f78205d..8baa664653 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -571,6 +571,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" }, { "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" }, { "TagAction", true, "name, value", "set the tag named 'name' to the given value" }, { "TagResponseAction", true, "name, value", "set the tag named 'name' to the given value" }, diff --git a/pdns/dnsdist-lua-actions.cc b/pdns/dnsdist-lua-actions.cc index cc714d23d3..54fe838590 100644 --- a/pdns/dnsdist-lua-actions.cc +++ b/pdns/dnsdist-lua-actions.cc @@ -404,19 +404,24 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu { uint16_t qtype = dq->qtype; // do we even have a response? - if(d_cname.empty() && !std::count_if(d_addrs.begin(), d_addrs.end(), [qtype](const ComboAddress& a) - { - return (qtype == QType::ANY || ((a.sin4.sin_family == AF_INET && qtype == QType::A) || - (a.sin4.sin_family == AF_INET6 && qtype == QType::AAAA))); - })) + if (d_cname.empty() && + d_rawResponse.empty() && + d_types.count(qtype) == 0) { return Action::None; + } vector addrs; - unsigned int totrdatalen=0; + unsigned int totrdatalen = 0; + uint16_t numberOfRecords = 0; if (!d_cname.empty()) { qtype = QType::CNAME; totrdatalen += d_cname.toDNSString().size(); - } else { + numberOfRecords = 1; + } else if (!d_rawResponse.empty()) { + totrdatalen += d_rawResponse.size(); + numberOfRecords = 1; + } + else { for(const auto& addr : d_addrs) { if(qtype != QType::ANY && ((addr.sin4.sin_family == AF_INET && qtype != QType::A) || (addr.sin4.sin_family == AF_INET6 && qtype != QType::AAAA))) { @@ -424,6 +429,7 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu } totrdatalen += addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr); addrs.push_back(addr); + ++numberOfRecords; } } @@ -433,7 +439,7 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu unsigned int consumed=0; DNSName ignore((char*)dq->dh, dq->len, sizeof(dnsheader), false, 0, 0, &consumed); - if (dq->size < (sizeof(dnsheader) + consumed + 4 + ((d_cname.empty() ? 0 : 1) + addrs.size())*12 /* recordstart */ + totrdatalen)) { + if (dq->size < (sizeof(dnsheader) + consumed + 4 + numberOfRecords*12 /* recordstart */ + totrdatalen)) { return Action::None; } @@ -452,14 +458,21 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu dq->dh->ancount = 0; dq->dh->arcount = 0; // for now, forget about your EDNS, we're marching over it - if(qtype == QType::CNAME) { - std::string wireData = d_cname.toDNSString(); // Note! This doesn't do compression! - const unsigned char recordstart[]={0xc0, 0x0c, // compressed name - 0, (unsigned char) qtype, - 0, QClass::IN, // IN - 0, 0, 0, 60, // TTL - 0, (unsigned char)wireData.length()}; - static_assert(sizeof(recordstart) == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid"); + uint32_t ttl = htonl(d_responseConfig.ttl); + unsigned char recordstart[] = {0xc0, 0x0c, // compressed name + 0, 0, // QTYPE + 0, QClass::IN, + 0, 0, 0, 0, // TTL + 0, 0 }; // rdata length + static_assert(sizeof(recordstart) == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid"); + memcpy(&recordstart[6], &ttl, sizeof(ttl)); + + if (qtype == QType::CNAME) { + const std::string wireData = d_cname.toDNSString(); // Note! This doesn't do compression! + uint16_t rdataLen = htons(wireData.length()); + qtype = htons(qtype); + memcpy(&recordstart[2], &qtype, sizeof(qtype)); + memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen)); memcpy(dest, recordstart, sizeof(recordstart)); dest += sizeof(recordstart); @@ -467,24 +480,33 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu dq->len += wireData.length() + sizeof(recordstart); dq->dh->ancount++; } + else if (!d_rawResponse.empty()) { + uint16_t rdataLen = htons(d_rawResponse.size()); + qtype = htons(qtype); + 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->len += d_rawResponse.size() + sizeof(recordstart); + dq->dh->ancount++; + } else { for(const auto& addr : addrs) { - unsigned char rdatalen = addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr); - const unsigned char recordstart[]={0xc0, 0x0c, // compressed name - 0, (unsigned char) (addr.sin4.sin_family == AF_INET ? QType::A : QType::AAAA), - 0, QClass::IN, // IN - 0, 0, 0, 60, // TTL - 0, rdatalen}; - static_assert(sizeof(recordstart) == 12, "sizeof(recordstart) must be equal to 12, otherwise the above check is invalid"); + uint16_t rdataLen = htons(addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr)); + qtype = htons(addr.sin4.sin_family == AF_INET ? QType::A : QType::AAAA); + memcpy(&recordstart[2], &qtype, sizeof(qtype)); + memcpy(&recordstart[10], &rdataLen, sizeof(rdataLen)); memcpy(dest, recordstart, sizeof(recordstart)); dest += sizeof(recordstart); memcpy(dest, addr.sin4.sin_family == AF_INET ? (void*)&addr.sin4.sin_addr.s_addr : (void*)&addr.sin6.sin6_addr.s6_addr, - rdatalen); - dest += rdatalen; - dq->len += rdatalen + sizeof(recordstart); + addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr)); + dest += (addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr)); + dq->len += (addr.sin4.sin_family == AF_INET ? sizeof(addr.sin4.sin_addr.s_addr) : sizeof(addr.sin6.sin6_addr.s6_addr)) + sizeof(recordstart); dq->dh->ancount++; } } @@ -1250,11 +1272,14 @@ static void addAction(GlobalStateHolder > *someRulActions, const luadn }); } -typedef std::unordered_map > responseParams_t; +typedef std::unordered_map > responseParams_t; static void parseResponseConfig(boost::optional vars, ResponseConfig& config) { if (vars) { + if (vars->count("ttl")) { + config.ttl = boost::get((*vars)["ttl"]); + } if (vars->count("aa")) { config.setAA = boost::get((*vars)["aa"]); } @@ -1398,11 +1423,16 @@ void setupLuaActions() }); g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a, boost::optional vars) { - auto ret = std::shared_ptr(new SpoofAction(a)); - ResponseConfig responseConfig; - parseResponseConfig(vars, responseConfig); + auto ret = std::shared_ptr(new SpoofAction(DNSName(a))); auto sa = std::dynamic_pointer_cast(ret); - sa->d_responseConfig = responseConfig; + parseResponseConfig(vars, sa->d_responseConfig); + return ret; + }); + + g_lua.writeFunction("SpoofRawAction", [](const std::string& raw, boost::optional vars) { + auto ret = std::shared_ptr(new SpoofAction(raw)); + auto sa = std::dynamic_pointer_cast(ret); + parseResponseConfig(vars, sa->d_responseConfig); return ret; }); diff --git a/pdns/dnsdist-lua-bindings.cc b/pdns/dnsdist-lua-bindings.cc index 2cfb6f61e8..06cc42f9da 100644 --- a/pdns/dnsdist-lua-bindings.cc +++ b/pdns/dnsdist-lua-bindings.cc @@ -147,7 +147,7 @@ void setupLuaBindings(bool client) g_lua.registerFunction("setAA", [](dnsheader& dh, bool v) { dh.aa=v; - }); + }); g_lua.registerFunction("getAA", [](dnsheader& dh) { return (bool)dh.aa; diff --git a/pdns/dnsdist-lua-vars.cc b/pdns/dnsdist-lua-vars.cc index f10c6c3d37..b76a05cfee 100644 --- a/pdns/dnsdist-lua-vars.cc +++ b/pdns/dnsdist-lua-vars.cc @@ -31,6 +31,7 @@ void setupLuaVars() {"Nxdomain", (int)DNSAction::Action::Nxdomain}, {"Refused", (int)DNSAction::Action::Refused}, {"Spoof", (int)DNSAction::Action::Spoof}, + {"SpoofRaw", (int)DNSAction::Action::SpoofRaw}, {"Allow", (int)DNSAction::Action::Allow}, {"HeaderModify", (int)DNSAction::Action::HeaderModify}, {"Pool", (int)DNSAction::Action::Pool}, diff --git a/pdns/dnsdist-lua.hh b/pdns/dnsdist-lua.hh index aa33859c80..021e124819 100644 --- a/pdns/dnsdist-lua.hh +++ b/pdns/dnsdist-lua.hh @@ -26,6 +26,7 @@ struct ResponseConfig boost::optional setAA{boost::none}; boost::optional setAD{boost::none}; boost::optional setRA{boost::none}; + uint32_t ttl{60}; }; void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config); @@ -64,17 +65,40 @@ class SpoofAction : public DNSAction public: SpoofAction(const vector& addrs): d_addrs(addrs) { + for (const auto& addr : d_addrs) { + if (addr.isIPv4()) { + d_types.insert(QType::A); + } + else if (addr.isIPv6()) { + d_types.insert(QType::AAAA); + } + } + + if (!d_addrs.empty()) { + d_types.insert(QType::ANY); + } } - SpoofAction(const string& cname): d_cname(cname) + + SpoofAction(const DNSName& cname): d_cname(cname) { } + + SpoofAction(const std::string& raw): d_rawResponse(raw) + { + } + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override; + string toString() const override { string ret = "spoof in "; - if(!d_cname.empty()) { - ret+=d_cname.toString()+ " "; - } else { + if (!d_cname.empty()) { + ret += d_cname.toString() + " "; + } + else if (!d_rawResponse.empty()) { + ret += "raw bytes "; + } + else { for(const auto& a : d_addrs) ret += a.toString()+" "; } @@ -85,6 +109,8 @@ public: ResponseConfig d_responseConfig; private: std::vector d_addrs; + std::set d_types; + std::string d_rawResponse; DNSName d_cname; }; diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 96af451cf7..7c584c0e99 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -1031,34 +1031,41 @@ NumberedServerVector getDownstreamCandidates(const pools_t& pools, const std::st return pool->getServers(); } -static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent) +static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent, bool raw) { string result; - std::vector addrs; - stringtok(addrs, spoofContent, " ,"); + if (raw) { + SpoofAction sa(spoofContent); + sa(&dq, &result); + } + else { + std::vector addrs; + stringtok(addrs, spoofContent, " ,"); - if (addrs.size() == 1) { - try { - ComboAddress spoofAddr(spoofContent); - SpoofAction sa({spoofAddr}); - sa(&dq, &result); - } - catch(const PDNSException &e) { - SpoofAction sa(spoofContent); // CNAME then - sa(&dq, &result); - } - } else { - std::vector cas; - for (const auto& addr : addrs) { + if (addrs.size() == 1) { try { - cas.push_back(ComboAddress(addr)); + ComboAddress spoofAddr(spoofContent); + SpoofAction sa({spoofAddr}); + sa(&dq, &result); } - catch (...) { + catch(const PDNSException &e) { + DNSName cname(spoofContent); + SpoofAction sa(cname); // CNAME then + sa(&dq, &result); } + } else { + std::vector cas; + for (const auto& addr : addrs) { + try { + cas.push_back(ComboAddress(addr)); + } + catch (...) { + } + } + SpoofAction sa(cas); + sa(&dq, &result); } - SpoofAction sa(cas); - sa(&dq, &result); } } @@ -1092,7 +1099,11 @@ bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::s return true; break; case DNSAction::Action::Spoof: - spoofResponseFromString(dq, ruleresult); + spoofResponseFromString(dq, ruleresult, false); + return true; + break; + case DNSAction::Action::SpoofRaw: + spoofResponseFromString(dq, ruleresult, true); return true; break; case DNSAction::Action::Truncate: diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index 9c65edc874..791b99a194 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -132,7 +132,7 @@ struct DNSResponse : DNSQuestion class DNSAction { public: - enum class Action { Drop, Nxdomain, Refused, Spoof, Allow, HeaderModify, Pool, Delay, Truncate, ServFail, None, NoOp, NoRecurse }; + enum class Action { Drop, Nxdomain, Refused, Spoof, Allow, HeaderModify, Pool, Delay, Truncate, ServFail, None, NoOp, NoRecurse, SpoofRaw }; static std::string typeToString(const Action& action) { switch(action) { @@ -144,6 +144,8 @@ public: return "Send Refused"; case Action::Spoof: return "Spoof an answer"; + case Action::SpoofRaw: + return "Spoof an answer from raw bytes"; case Action::Allow: return "Allow"; case Action::HeaderModify: diff --git a/pdns/dnsdistdist/docs/reference/constants.rst b/pdns/dnsdistdist/docs/reference/constants.rst index 1ead1ec82e..aba032b706 100644 --- a/pdns/dnsdistdist/docs/reference/constants.rst +++ b/pdns/dnsdistdist/docs/reference/constants.rst @@ -102,6 +102,9 @@ These constants represent the section in the DNS Packet. DNSAction --------- +.. versionchanged:: 1.5.0 + ``DNSAction.SpoofRaw`` has been added. + These constants represent an Action that can be returned from :func:`LuaAction` functions. * ``DNSAction.Allow``: let the query pass, skipping other rules @@ -115,6 +118,7 @@ These constants represent an Action that can be returned from :func:`LuaAction` * ``DNSAction.Refused``: return a response with a Refused rcode * ``DNSAction.ServFail``: return a response with a ServFail rcode * ``DNSAction.Spoof``: spoof the response using the supplied IPv4 (A), IPv6 (AAAA) or string (CNAME) value + * ``DNSAction.SpoofRaw``: spoof the response using the supplied raw value as record data * ``DNSAction.Truncate``: truncate the response * ``DNSAction.NoRecurse``: set rd=0 on the query diff --git a/pdns/dnsdistdist/docs/rules-actions.rst b/pdns/dnsdistdist/docs/rules-actions.rst index 1d698b69d5..d1e18c5703 100644 --- a/pdns/dnsdistdist/docs/rules-actions.rst +++ b/pdns/dnsdistdist/docs/rules-actions.rst @@ -1223,6 +1223,7 @@ The following actions exist. * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it. * ``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. .. function:: SpoofCNAMEAction(cname [, options]) @@ -1239,6 +1240,24 @@ The following actions exist. * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it. * ``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. + +.. function:: SpoofRawAction(rawAnswer [, options]) + + .. versionadded:: 1.5.0 + + Forge a response with the specified raw bytes as record data. + For example, for a TXT record of "aaa" "bbbb": SpoofRawAction("\003aaa\004bbbb") + + :param string rawAnswer: The raw record data + :param table options: A table with key: value pairs with options. + + Options: + + * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it. + * ``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. .. function:: TagAction(name, value) diff --git a/regression-tests.dnsdist/test_Spoofing.py b/regression-tests.dnsdist/test_Spoofing.py index 2ae03bf866..fa81ccf263 100644 --- a/regression-tests.dnsdist/test_Spoofing.py +++ b/regression-tests.dnsdist/test_Spoofing.py @@ -12,6 +12,9 @@ class TestSpoofingSpoof(DNSDistTest): addAction(makeRule("spoofaction-nora.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {ra=false})) addAction(makeRule("cnamespoofaction.spoofing.tests.powerdns.com."), SpoofCNAMEAction("cnameaction.spoofing.tests.powerdns.com.")) addAction("multispoof.spoofing.tests.powerdns.com", SpoofAction({"192.0.2.1", "192.0.2.2", "2001:DB8::1", "2001:DB8::2"})) + 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 })) newServer{address="127.0.0.1:%s"} """ @@ -195,6 +198,7 @@ class TestSpoofingSpoof(DNSDistTest): (_, receivedResponse) = sender(query, response=None, useQueue=False) self.assertTrue(receivedResponse) self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.answer[0].ttl, 60) def testSpoofActionSetAD(self): """ @@ -218,6 +222,7 @@ class TestSpoofingSpoof(DNSDistTest): (_, receivedResponse) = sender(query, response=None, useQueue=False) self.assertTrue(receivedResponse) self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.answer[0].ttl, 60) def testSpoofActionSetRA(self): """ @@ -241,6 +246,7 @@ class TestSpoofingSpoof(DNSDistTest): (_, receivedResponse) = sender(query, response=None, useQueue=False) self.assertTrue(receivedResponse) self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.answer[0].ttl, 60) def testSpoofActionSetNoRA(self): """ @@ -262,6 +268,71 @@ class TestSpoofingSpoof(DNSDistTest): (_, receivedResponse) = sender(query, response=None, useQueue=False) self.assertTrue(receivedResponse) self.assertEquals(expectedResponse, receivedResponse) + self.assertEquals(receivedResponse.answer[0].ttl, 60) + + def testSpoofRawAction(self): + """ + Spoofing: Spoof a response from raw bytes + """ + name = 'raw.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') + 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) + + # SRV + query = dns.message.make_query(name, 'SRV', 'IN') + query.flags &= ~dns.flags.RD + expectedResponse = dns.message.make_response(query) + # this one should have the AA flag set + expectedResponse.flags |= dns.flags.AA + rrset = dns.rrset.from_text(name, + 3600, + dns.rdataclass.IN, + dns.rdatatype.SRV, + '0 0 65535 srv.powerdns.com.') + 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, 3600) class TestSpoofingLuaSpoof(DNSDistTest): @@ -277,11 +348,29 @@ class TestSpoofingLuaSpoof(DNSDistTest): return DNSAction.None, "" end end + function spoof2rule(dq) return DNSAction.Spoof, "spoofedcname.spoofing.tests.powerdns.com." end + + 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 })) + + function spoofrawrule(dq) + if dq.qtype == DNSQType.A then + return DNSAction.SpoofRaw, "\\192\\000\\002\\001" + elseif dq.qtype == DNSQType.TXT then + return DNSAction.SpoofRaw, "\\003aaa\\004bbbb\\011ccccccccccc" + elseif dq.qtype == DNSQType.SRV then + dq.dh:setAA(true) + return DNSAction.SpoofRaw, "\\000\\000\\000\\000\\255\\255\\003srv\\008powerdns\\003com\\000" + end + return DNSAction.None, "" + end + addAction("luaspoof1.spoofing.tests.powerdns.com.", LuaAction(spoof1rule)) addAction("luaspoof2.spoofing.tests.powerdns.com.", LuaAction(spoof2rule)) + addAction("lua-raw.spoofing.tests.powerdns.com.", LuaAction(spoofrawrule)) newServer{address="127.0.0.1:%s"} """ @@ -385,6 +474,71 @@ class TestSpoofingLuaSpoof(DNSDistTest): self.assertTrue(receivedResponse) self.assertEquals(expectedResponse, receivedResponse) + def testLuaSpoofRawAction(self): + """ + Spoofing: Spoof a response from raw bytes via Lua + """ + name = 'lua-raw.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') + 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) + + # SRV + query = dns.message.make_query(name, 'SRV', 'IN') + query.flags &= ~dns.flags.RD + expectedResponse = dns.message.make_response(query) + # this one should have the AA flag set + expectedResponse.flags |= dns.flags.AA + rrset = dns.rrset.from_text(name, + 3600, + dns.rdataclass.IN, + dns.rdatatype.SRV, + '0 0 65535 srv.powerdns.com.') + 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) + # sorry, we can't set the TTL from the Lua API right now + #self.assertEquals(receivedResponse.answer[0].ttl, 3600) + class TestSpoofingLuaWithStatistics(DNSDistTest): _config_template = """