From: Remi Gacogne Date: Fri, 15 Jan 2016 11:00:01 +0000 (+0100) Subject: dnsdist: Implement DNSAction.Spoof. Support IPv6-only SpoofAction X-Git-Tag: dnsdist-1.0.0-alpha2~76^2 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=7791f83a7846e0bb9a51c91f07a586cd0385643d;p=pdns dnsdist: Implement DNSAction.Spoof. Support IPv6-only SpoofAction DNSAction.Spoof can be used to return a spoofed response from a Lua rule. It supports an IPv4 (A), IPv6 (AAAA) or a DNSName (CNAME). SpoofAction() can be used IPv6-only, by passing a IPv6 as the first parameter. It now supports spoofing IPv4-only, IPv6-only, IPv4 and IPv6, and CNAME. Closes #3064. --- diff --git a/pdns/README-dnsdist.md b/pdns/README-dnsdist.md index 232d12636..1ffd879d5 100644 --- a/pdns/README-dnsdist.md +++ b/pdns/README-dnsdist.md @@ -428,7 +428,7 @@ Valid return values for `LuaAction` functions are: * DNSAction.None: continue to the next rule * DNSAction.Nxdomain: return a response with a NXDomain rcode * DNSAction.Pool: use the specified pool to forward this query - * DNSAction.Spoof: currently not implemented, see addDomainSpoof() and SpoofAction() + * DNSAction.Spoof: spoof the response using the supplied IPv4 (A), IPv6 (AAAA) or string (CNAME) value DNSSEC ------ @@ -828,12 +828,12 @@ instantiate a server with additional parameters * `QPSPoolAction()`: set the packet into the specified pool only if it does not exceed the specified QPS limits * `QPSAction()`: drop these packets if the QPS limits are exceeded * `RCodeAction()`: reply immediatly by turning the query into a response with the specified rcode - * `SpoofAction()`: forge a response with the specified IPv4 (for an A query). If you specify both an IPv4 and an IPv6, IPv4 will be used for A and IPv6 for an AAAA + * `SpoofAction()`: forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify two addresses, the first one should be an IPv4 and will be used for A, the second an IPv6 for an AAAA * `SpoofCNAMEAction()`: forge a response with the specified CNAME value * `TCAction()`: create answer to query with TC and RD bits set, to move to TCP/IP * Specialist rule generators * `addAnyTCRule()`: generate TC=1 answers to ANY queries, moving them to TCP - * `addDomainSpoof(domain, ip[, ip6])`: generate answers for A queries using the ip parameter. If ip6 is supplied, generate answers for AAAA queries too + * `addDomainSpoof(domain, ip[, ip6])`: generate answers for A queries using the ip parameter (AAAA if ip is an IPv6). If ip6 is supplied, generate answers for AAAA queries too * `addDomainCNAMESpoof(domain, cname)`: generate CNAME answers for queries using the specified value * `addDisableValidationRule(domain)`: set the CD flags to 1 for all queries matching the specified domain * `addNoRecurseRule(domain)`: clear the RD flag for all queries matching the specified domain diff --git a/pdns/dnsdist-tcp.cc b/pdns/dnsdist-tcp.cc index e70599d65..265c23e77 100644 --- a/pdns/dnsdist-tcp.cc +++ b/pdns/dnsdist-tcp.cc @@ -275,7 +275,8 @@ void* tcpClientThread(int pipefd) break; case DNSAction::Action::Spoof: - ; + spoofResponseFromString(ci.remote, qname, qtype, dh, queryLen, querySize, ruleresult); + /* fall-through */; case DNSAction::Action::HeaderModify: break; case DNSAction::Action::Allow: diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 6b9b38010..d872939e6 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -482,6 +482,20 @@ int getEDNSZ(const char* packet, unsigned int len) return 0x100 * (*z) + *(z+1); } +void spoofResponseFromString(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t querySize, const string& spoofContent) +{ + string result; + try { + ComboAddress spoofAddr(spoofContent); + SpoofAction sa(spoofAddr); + sa(remote, qname, qtype, dh, len, querySize, &result); + } + catch(PDNSException &e) { + SpoofAction sa(spoofContent); + sa(remote, qname, qtype, dh, len, querySize, &result); + } +} + static ssize_t udpClientSendRequestToBackend(DownstreamState* ss, const int sd, const char* request, const size_t requestLen) { if (ss->sourceItf == 0) { @@ -647,7 +661,8 @@ try pool=ruleresult; break; case DNSAction::Action::Spoof: - ; + spoofResponseFromString(remote, qname, qtype, dh, len, querySize, ruleresult); + /* fall-through */; case DNSAction::Action::HeaderModify: break; case DNSAction::Action::Delay: diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index ed4d873b5..81dc83f32 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -440,6 +440,7 @@ std::shared_ptr wrandom(const NumberedServerVector& servers, co std::shared_ptr whashed(const NumberedServerVector& servers, const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh); std::shared_ptr roundrobin(const NumberedServerVector& servers, const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh); int getEDNSZ(const char* packet, unsigned int len); +void spoofResponseFromString(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t querySize, const string& spoofContent); uint16_t getEDNSOptionCode(const char * packet, size_t len); void dnsdistWebserverThread(int sock, const ComboAddress& local, const string& password); bool getMsgLen32(int fd, uint32_t* len); diff --git a/pdns/dnsrulactions.hh b/pdns/dnsrulactions.hh index 323ee2b61..3b14651df 100644 --- a/pdns/dnsrulactions.hh +++ b/pdns/dnsrulactions.hh @@ -356,7 +356,16 @@ public: class SpoofAction : public DNSAction { public: - SpoofAction(const ComboAddress& a) : d_a(a) { d_aaaa.sin4.sin_family = 0;} + SpoofAction(const ComboAddress& a) + { + 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; } DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override diff --git a/regression-tests.dnsdist/test_Advanced.py b/regression-tests.dnsdist/test_Advanced.py index a951f2fdd..875a0a0fb 100644 --- a/regression-tests.dnsdist/test_Advanced.py +++ b/regression-tests.dnsdist/test_Advanced.py @@ -545,3 +545,141 @@ class TestAdvancedDelay(DNSDistTest): self.assertEquals(query, receivedQuery) self.assertEquals(response, receivedResponse) self.assertTrue((end - begin) < timedelta(0, 1)); + +class TestAdvancedLuaSpoof(DNSDistTest): + + _config_template = """ + function spoof1rule(remote, qname, qtype, dh, len) + if(qtype==1) -- A + then + return DNSAction.Spoof, "192.0.2.1" + elseif(qtype == 28) -- AAAA + then + return DNSAction.Spoof, "2001:DB8::1" + else + return DNSAction.None, "" + end + end + function spoof2rule(remote, qname, qtype, dh, len) + return DNSAction.Spoof, "spoofedcname.tests.powerdns.com." + end + addLuaAction("luaspoof1.tests.powerdns.com.", spoof1rule) + addLuaAction("luaspoof2.tests.powerdns.com.", spoof2rule) + newServer{address="127.0.0.1:%s"} + """ + + def testLuaSpoofA(self): + """ + Advanced: Spoofing an A via Lua + + Send an A query to "luaspoof1.tests.powerdns.com.", + check that dnsdist sends a spoofed result. + """ + name = 'luaspoof1.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.1') + expectedResponse.answer.append(rrset) + + (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + receivedResponse.id = expectedResponse.id + self.assertEquals(expectedResponse, receivedResponse) + + (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + receivedResponse.id = expectedResponse.id + self.assertEquals(expectedResponse, receivedResponse) + + def testLuaSpoofAAAA(self): + """ + Advanced: Spoofing an AAAA via Lua + + Send an AAAA query to "luaspoof1.tests.powerdns.com.", + check that dnsdist sends a spoofed result. + """ + name = 'luaspoof1.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') + expectedResponse.answer.append(rrset) + + (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + receivedResponse.id = expectedResponse.id + self.assertEquals(expectedResponse, receivedResponse) + + (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + receivedResponse.id = expectedResponse.id + self.assertEquals(expectedResponse, receivedResponse) + + def testLuaSpoofAWithCNAME(self): + """ + Advanced: Spoofing an A with a CNAME via Lua + + Send an A query to "luaspoof2.tests.powerdns.com.", + check that dnsdist sends a spoofed result. + """ + name = 'luaspoof2.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.CNAME, + 'spoofedcname.tests.powerdns.com.') + expectedResponse.answer.append(rrset) + + (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + receivedResponse.id = expectedResponse.id + self.assertEquals(expectedResponse, receivedResponse) + + (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + receivedResponse.id = expectedResponse.id + self.assertEquals(expectedResponse, receivedResponse) + + def testLuaSpoofAAAAWithCNAME(self): + """ + Advanced: Spoofing an AAAA with a CNAME via Lua + + Send an AAAA query to "luaspoof2.tests.powerdns.com.", + check that dnsdist sends a spoofed result. + """ + name = 'luaspoof2.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.CNAME, + 'spoofedcname.tests.powerdns.com.') + expectedResponse.answer.append(rrset) + + (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + receivedResponse.id = expectedResponse.id + self.assertEquals(expectedResponse, receivedResponse) + + (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) + self.assertTrue(receivedResponse) + receivedResponse.id = expectedResponse.id + self.assertEquals(expectedResponse, receivedResponse)