From: bert hubert Date: Sat, 20 Feb 2016 20:58:41 +0000 (+0100) Subject: make dnsdist spoofing actions support multiple A and AAAA records which we'll shuffle... X-Git-Tag: auth-4.0.0-alpha2~19^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9ebc0e916a6eb7dd4dbbd6ab58f25f4e698157ef;p=thirdparty%2Fpdns.git make dnsdist spoofing actions support multiple A and AAAA records which we'll shuffle and include, plus regression tests for same --- diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 3848d2b975..18b7920bbe 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -573,37 +573,51 @@ vector> setupLua(bool client, const std::string& confi return std::shared_ptr(new QPSPoolAction(limit, a)); }); - g_lua.writeFunction("SpoofAction", [](const string& a, boost::optional b) { - if(b) - return std::shared_ptr(new SpoofAction(ComboAddress(a), ComboAddress(*b))); - else - return std::shared_ptr(new SpoofAction(ComboAddress(a))); + g_lua.writeFunction("SpoofAction", [](boost::variant>> inp, boost::optional b ) { + vector addrs; + if(auto s = boost::get(&inp)) + addrs.push_back(ComboAddress(*s)); + else { + const auto& v = boost::get>>(inp); + for(const auto& a: v) + addrs.push_back(ComboAddress(a.second)); + } + if(b) + addrs.push_back(ComboAddress(*b)); + return std::shared_ptr(new SpoofAction(addrs)); }); g_lua.writeFunction("SpoofCNAMEAction", [](const string& a) { return std::shared_ptr(new SpoofAction(a)); }); - g_lua.writeFunction("addDomainSpoof", [](const std::string& domain, const std::string& ip, boost::optional ip6) { + g_lua.writeFunction("addDomainSpoof", [](const std::string& domain, boost::variant>> inp, boost::optional b) { setLuaSideEffect(); SuffixMatchNode smn; - ComboAddress a, b; - b.sin6.sin6_family=0; + vector outp; try { smn.add(DNSName(domain)); - a=ComboAddress(ip); - if(ip6) - b=ComboAddress(*ip6); + + if(auto s = boost::get(&inp)) + outp.push_back(ComboAddress(*s)); + else { + const auto& v = boost::get>>(inp); + for(const auto& a: v) + outp.push_back(ComboAddress(a.second)); + } + if(b) + outp.push_back(ComboAddress(*b)); + } catch(std::exception& e) { g_outputBuffer="Error parsing parameters: "+string(e.what()); return; } - g_rulactions.modify([&smn,&a,&b](decltype(g_rulactions)::value_type& rulactions) { + g_rulactions.modify([&smn,&outp](decltype(g_rulactions)::value_type& rulactions) { rulactions.push_back({ std::make_shared(smn), - std::make_shared(a, b) }); + std::make_shared(outp) }); }); }); diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 87cadb5585..7471084a65 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -559,11 +559,11 @@ void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent) string result; try { ComboAddress spoofAddr(spoofContent); - SpoofAction sa(spoofAddr); + SpoofAction sa({spoofAddr}); sa(&dq, &result); } catch(PDNSException &e) { - SpoofAction sa(spoofContent); + SpoofAction sa(spoofContent); // CNAME then sa(&dq, &result); } } diff --git a/pdns/dnsdistconf.lua b/pdns/dnsdistconf.lua index 10581f89bb..aa54948478 100644 --- a/pdns/dnsdistconf.lua +++ b/pdns/dnsdistconf.lua @@ -3,7 +3,7 @@ webserver("0.0.0.0:8083", "geheim2") addLocal("0.0.0.0:5200") setKey("MXNeLFWHUe4363BBKrY06cAsH8NWNb+Se2eXU5+Bb74=") truncateTC(true) -- fix up possibly badly truncated answers from pdns 2.9.22 -carbonServer("2001:888:2000:1d::2") +-- carbonServer("2001:888:2000:1d::2") warnlog(string.format("Script starting %s", "up!")) diff --git a/pdns/dnsrulactions.hh b/pdns/dnsrulactions.hh index bee5b643aa..8ebb09dc69 100644 --- a/pdns/dnsrulactions.hh +++ b/pdns/dnsrulactions.hh @@ -473,85 +473,110 @@ public: class SpoofAction : public DNSAction { public: - SpoofAction(const ComboAddress& a) + SpoofAction(const vector& addrs) : d_addrs(addrs) { - if (a.sin4.sin_family == AF_INET) { - d_a = a; - d_aaaa.sin4.sin_family = 0; - } else { - d_a.sin4.sin_family = 0; - d_aaaa = a; - } } - SpoofAction(const ComboAddress& a, const ComboAddress& aaaa) : d_a(a), d_aaaa(aaaa) {} - SpoofAction(const string& cname): d_cname(cname) { d_a.sin4.sin_family = 0; d_aaaa.sin4.sin_family = 0; } + + SpoofAction(const string& cname): d_cname(cname) { } + DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override { uint16_t qtype = dq->qtype; - if(d_cname.empty() && - ((qtype == QType::A && d_a.sin4.sin_family == 0) || - (qtype == QType::AAAA && d_aaaa.sin4.sin_family == 0) || (qtype != QType::A && qtype != QType::AAAA))) + // 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))); + })) return Action::None; - - unsigned int consumed=0; - uint8_t rdatalen = 0; + + vector addrs; + unsigned int totrdatalen=0; if (!d_cname.empty()) { qtype = QType::CNAME; - rdatalen = d_cname.wirelength(); + totrdatalen += d_cname.toDNSString().size(); } else { - rdatalen = qtype == QType::A ? 4 : 16; + 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))) + continue; + totrdatalen += addr.sin4.sin_family == AF_INET ? 4 : 16; + addrs.push_back(addr); + } } - const unsigned char recordstart[]={0xc0, 0x0c, // compressed name - 0, (unsigned char) qtype, - 0, QClass::IN, // IN - 0, 0, 0, 60, // TTL - 0, rdatalen}; + if(addrs.size() > 1) + random_shuffle(addrs.begin(), addrs.end()); + unsigned int consumed=0; DNSName ignore((char*)dq->dh, dq->len, sizeof(dnsheader), false, 0, 0, &consumed); - if (dq->size < (sizeof(dnsheader) + consumed + 4 + sizeof(recordstart) + rdatalen)) { + if (dq->size < (sizeof(dnsheader) + consumed + 4 + ((d_cname.empty() ? 0 : 1) + addrs.size())*12 /* recordstart */ + totrdatalen)) { return Action::None; } + dq->len = sizeof(dnsheader) + consumed + 4; // there goes your EDNS + char* dest = ((char*)dq->dh) + dq->len; + dq->dh->qr = true; // for good measure dq->dh->ra = dq->dh->rd; // for good measure dq->dh->ad = false; - dq->dh->ancount = htons(1); + dq->dh->ancount = 0; dq->dh->arcount = 0; // for now, forget about your EDNS, we're marching over it - char* dest = ((char*)dq->dh) +sizeof(dnsheader) + consumed + 4; - memcpy(dest, recordstart, sizeof(recordstart)); - if(qtype==QType::A) - memcpy(dest+sizeof(recordstart), &d_a.sin4.sin_addr.s_addr, 4); - else if (qtype==QType::AAAA) - memcpy(dest+sizeof(recordstart), &d_aaaa.sin6.sin6_addr.s6_addr, 16); - else if (qtype==QType::CNAME) { - string wireData = d_cname.toDNSString(); - memcpy(dest+sizeof(recordstart), wireData.c_str(), wireData.length()); + if(qtype == QType::CNAME) { + 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()}; + + + memcpy(dest, recordstart, sizeof(recordstart)); + dest += sizeof(recordstart); + memcpy(dest, wireData.c_str(), wireData.length()); + dq->len += wireData.length() + sizeof(recordstart); + dq->dh->ancount++; } - dq->len = (dest + sizeof(recordstart) + rdatalen) - (char*)dq->dh; + else for(const auto& addr : addrs) + { + unsigned char rdatalen = addr.sin4.sin_family == AF_INET ? 4 : 16; + 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}; + + memcpy(dest, recordstart, sizeof(recordstart)); + dest += sizeof(recordstart); + + memcpy(dest, + rdatalen==4 ? (void*)&addr.sin4.sin_addr.s_addr : (void*)&addr.sin6.sin6_addr.s6_addr, + rdatalen); + dest += rdatalen; + dq->len += rdatalen + sizeof(recordstart); + dq->dh->ancount++; + } + + dq->dh->ancount = htons(dq->dh->ancount); + return Action::HeaderModify; } + string toString() const override { - string ret; + string ret = "spoof in "; if(!d_cname.empty()) { - if(!ret.empty()) ret += ", "; - ret+="spoof in "+d_cname.toString(); + ret+=d_cname.toString()+ " "; } else { - if(d_a.sin4.sin_family) - ret="spoof in "+d_a.toString(); - if(d_aaaa.sin6.sin6_family) { - if(!ret.empty()) ret += ", "; - ret+="spoof in "+d_aaaa.toString(); - } + for(const auto& a : d_addrs) + ret += a.toString()+" "; } return ret; } private: - ComboAddress d_a; - ComboAddress d_aaaa; + std::vector d_addrs; DNSName d_cname; }; diff --git a/regression-tests.dnsdist/test_Advanced.py b/regression-tests.dnsdist/test_Advanced.py index 64bda34312..4d5be0123b 100644 --- a/regression-tests.dnsdist/test_Advanced.py +++ b/regression-tests.dnsdist/test_Advanced.py @@ -400,6 +400,7 @@ class TestAdvancedSpoof(DNSDistTest): addDomainCNAMESpoof("cnamespoof.advanced.tests.powerdns.com.", "cname.advanced.tests.powerdns.com.") addAction(makeRule("spoofaction.advanced.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1")) addAction(makeRule("cnamespoofaction.advanced.tests.powerdns.com."), SpoofCNAMEAction("cnameaction.advanced.tests.powerdns.com.")) + addDomainSpoof("multispoof.advanced.tests.powerdns.com", {"192.0.2.1", "192.0.2.2", "2001:DB8::1", "2001:DB8::2"}) newServer{address="127.0.0.1:%s"} """ @@ -565,6 +566,96 @@ class TestAdvancedSpoof(DNSDistTest): self.assertTrue(receivedResponse) self.assertEquals(expectedResponse, receivedResponse) + def testSpoofActionMultiA(self): + """ + Advanced: Spoof multiple IPv4 addresses via AddDomainSpoof + + Send an A query for "multispoof.advanced.tests.powerdns.com.", + check that dnsdist sends a spoofed result. + """ + name = 'multispoof.advanced.tests.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + # dnsdist set RA = RD for spoofed responses + query.flags &= ~dns.flags.RD + expectedResponse = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 60, + dns.rdataclass.IN, + dns.rdatatype.A, + '192.0.2.2', '192.0.2.1') + expectedResponse.answer.append(rrset) + + (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + self.assertEquals(expectedResponse, receivedResponse) + + (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + self.assertEquals(expectedResponse, receivedResponse) + + def testSpoofActionMultiAAAA(self): + """ + Advanced: Spoof multiple IPv6 addresses via AddDomainSpoof + + Send an AAAA query for "multispoof.advanced.tests.powerdns.com.", + check that dnsdist sends a spoofed result. + """ + name = 'multispoof.advanced.tests.powerdns.com.' + query = dns.message.make_query(name, 'AAAA', 'IN') + # dnsdist set RA = RD for spoofed responses + query.flags &= ~dns.flags.RD + expectedResponse = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 60, + dns.rdataclass.IN, + dns.rdatatype.AAAA, + '2001:DB8::1', '2001:DB8::2') + expectedResponse.answer.append(rrset) + + (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + self.assertEquals(expectedResponse, receivedResponse) + + (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + self.assertEquals(expectedResponse, receivedResponse) + + def testSpoofActionMultiANY(self): + """ + Advanced: Spoof multiple addresses via AddDomainSpoof + + Send an ANY query for "multispoof.advanced.tests.powerdns.com.", + check that dnsdist sends a spoofed result. + """ + name = 'multispoof.advanced.tests.powerdns.com.' + query = dns.message.make_query(name, 'ANY', 'IN') + # dnsdist set RA = RD for spoofed responses + query.flags &= ~dns.flags.RD + expectedResponse = dns.message.make_response(query) + + rrset = dns.rrset.from_text(name, + 60, + dns.rdataclass.IN, + dns.rdatatype.A, + '192.0.2.2', '192.0.2.1') + expectedResponse.answer.append(rrset) + + rrset = dns.rrset.from_text(name, + 60, + dns.rdataclass.IN, + dns.rdatatype.AAAA, + '2001:DB8::1', '2001:DB8::2') + expectedResponse.answer.append(rrset) + + (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + self.assertEquals(expectedResponse, receivedResponse) + + (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + self.assertEquals(expectedResponse, receivedResponse) + + class TestAdvancedPoolRouting(DNSDistTest): _config_template = """