]> granicus.if.org Git - pdns/commitdiff
dnsdist: Implement DNSAction.Spoof. Support IPv6-only SpoofAction
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 15 Jan 2016 11:00:01 +0000 (12:00 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 15 Jan 2016 11:00:01 +0000 (12:00 +0100)
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.

pdns/README-dnsdist.md
pdns/dnsdist-tcp.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsrulactions.hh
regression-tests.dnsdist/test_Advanced.py

index 232d126369314352fc3b790a8a9ac39a924d333c..1ffd879d52c36a029024bfec5fb190c8c12cfd9d 100644 (file)
@@ -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
index e70599d6553deae81114361c82b5b05067fd0c09..265c23e77abea0e17da818681560b2db1f12b955 100644 (file)
@@ -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:
index 6b9b38010f924c312d0d78adf631362a2353879a..d872939e6ebc3fe7c4a535162a3d06a40bc1ac53 100644 (file)
@@ -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:
index ed4d873b5b897df759b9d9079f323ff71a90950e..81dc83f32f9a206f41bb666d4bd4d67ff9cafafb 100644 (file)
@@ -440,6 +440,7 @@ std::shared_ptr<DownstreamState> wrandom(const NumberedServerVector& servers, co
 std::shared_ptr<DownstreamState> whashed(const NumberedServerVector& servers, const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh);
 std::shared_ptr<DownstreamState> 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);
index 323ee2b6156c9f9cc3fc813a374c97ade24a03b7..3b14651df844a6791e917d079b9801c8ffa25c91 100644 (file)
@@ -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
index a951f2fdd4f3e9694e35b3b0743d2c8cbf68ce4d..875a0a0fb72fc6dda672e873a369bccf3552c2f2 100644 (file)
@@ -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)