]> granicus.if.org Git - pdns/commitdiff
dnsdist: Add sending CNAME in spoofed responses
authorRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 5 Jan 2016 09:27:54 +0000 (10:27 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 5 Jan 2016 09:27:54 +0000 (10:27 +0100)
- Add addDomainCNAMESpoof() and SpoofCNAMEAction()
- Check that we have enough space in the buffer to write the response
- Implement the first part of #3064

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

index c9fe5329ec3cf468f7f8a84a1821d7c14d56c80c..0fe2196e131e045411087f7d1fc0ddab25ce6677 100644 (file)
@@ -280,6 +280,7 @@ Rules can be added via:
  * addDisableValidationRule(DNS rule)
  * addDomainBlock(domain)
  * addDomainSpoof(domain, IPv4[, IPv6])
+ * addDomainCNAMESpoof(domain, CNAME)
  * addLuaAction(DNS rule, lua function)
  * addNoRecurseRule(DNS rule)
  * addPoolRule(DNS rule, destination pool)
@@ -783,11 +784,13 @@ 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). If you specify both an IPv4 and an IPv6, IPv4 will be used for A and 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
+   * 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
    * setDNSSECPool(): move queries requesting DNSSEC processing to this pool
index 672597945a36f309552a467ccabe8a8c86f7c4a2..d4474f5d2e428e46cb7c77de07507356adaaf411 100644 (file)
@@ -15,13 +15,13 @@ static vector<std::function<void(void)>>* g_launchWork;
 class LuaAction : public DNSAction
 {
 public:
-  typedef std::function<std::tuple<int, string>(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, int len)> func_t;
+  typedef std::function<std::tuple<int, string>(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t len, uint16_t bufferSize)> func_t;
   LuaAction(LuaAction::func_t func) : d_func(func)
   {}
 
-  Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+  Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
   {
-    auto ret = d_func(remote, qname, qtype, dh, len);
+    auto ret = d_func(remote, qname, qtype, dh, len, bufferSize);
     if(ruleresult)
       *ruleresult=std::get<1>(ret);
     return (Action)std::get<0>(ret);
@@ -437,6 +437,10 @@ vector<std::function<void(void)>> setupLua(bool client, const std::string& confi
        return std::shared_ptr<DNSAction>(new SpoofAction(ComboAddress(a)));
     });
 
+  g_lua.writeFunction("SpoofCNAMEAction", [](const string& a) {
+      return std::shared_ptr<DNSAction>(new SpoofAction(a));
+    });
+
   g_lua.writeFunction("addDomainSpoof", [](const std::string& domain, const std::string& ip, boost::optional<string> ip6) { 
       setLuaSideEffect();
       SuffixMatchNode smn;
@@ -461,6 +465,23 @@ vector<std::function<void(void)>> setupLua(bool client, const std::string& confi
 
     });
 
+  g_lua.writeFunction("addDomainCNAMESpoof", [](const std::string& domain, const std::string& cname) {
+      setLuaSideEffect();
+      SuffixMatchNode smn;
+      try
+      {
+       smn.add(DNSName(domain));
+      }
+      catch(std::exception& e) {
+       g_outputBuffer="Error parsing parameters: "+string(e.what());
+       return;
+      }
+      g_rulactions.modify([&smn,&cname](decltype(g_rulactions)::value_type& rulactions) {
+         rulactions.push_back({
+             std::make_shared<SuffixMatchNodeRule>(smn),
+               std::make_shared<SpoofAction>(cname)  });
+       });
+    });
 
   g_lua.writeFunction("DropAction", []() {
       return std::shared_ptr<DNSAction>(new DropAction);
index ee25798cb8a16526559124241527e85b05c8d3fa..4c30629c95ccb94588ca61beca14961240b67018 100644 (file)
@@ -158,10 +158,13 @@ void* tcpClientThread(int pipefd)
           break;
         }
 
-        char queryBuffer[qlen];
+        /* if the query is small, allocate a bit more
+           memory to be able to spoof the content,
+           or to add ECS without allocating a new buffer */
+        size_t querySize = qlen <= 4096 ? qlen + 512 : qlen;
+        char queryBuffer[querySize];
         const char* query = queryBuffer;
         uint16_t queryLen = qlen;
-        size_t querySize = qlen;
         readn2WithTimeout(ci.fd, queryBuffer, queryLen, g_tcpRecvTimeout);
 #ifdef HAVE_DNSCRYPT
         std::shared_ptr<DnsCryptQuery> dnsCryptQuery = 0;
@@ -232,7 +235,7 @@ void* tcpClientThread(int pipefd)
        DNSAction::Action action=DNSAction::Action::None;
        for(const auto& lr : *localRulactions) {
          if(lr.first->matches(ci.remote, qname, qtype, dh, queryLen)) {
-           action=(*lr.second)(ci.remote, qname, qtype, dh, queryLen, &ruleresult);
+           action=(*lr.second)(ci.remote, qname, qtype, dh, queryLen, querySize, &ruleresult);
            if(action != DNSAction::Action::None) {
              lr.first->d_matches++;
              break;
index 43835eafa604d8aa38342e3086f9062335a7ca6c..be5633962d579c4714136d1eb07c053fc70baea1 100644 (file)
@@ -611,7 +611,7 @@ try
 
       for(const auto& lr : *localRulactions) {
        if(lr.first->matches(remote, qname, qtype, dh, len)) {
-         action=(*lr.second)(remote, qname, qtype, dh, len, &ruleresult);
+         action=(*lr.second)(remote, qname, qtype, dh, len, querySize, &ruleresult);
          if(action != DNSAction::Action::None) {
            lr.first->d_matches++;
            break;
index 400d4b959aeb44f333b52e68673c95652c41e6f2..09c014608dc26b72f732b9311dfc48adefc0d523 100644 (file)
@@ -366,7 +366,7 @@ class DNSAction
 {
 public:
   enum class Action { Drop, Nxdomain, Spoof, Allow, HeaderModify, Pool, Delay, None};
-  virtual Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const =0;
+  virtual Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const =0;
   virtual string toString() const = 0;
 };
 
index 91a05926c9a4e1d0fa7ab4aba66b549a3f21b70d..893720377d5b9b8e82313b4d1d93e87f320b100c 100644 (file)
@@ -214,7 +214,7 @@ private:
 class DropAction : public DNSAction
 {
 public:
-  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
   {
     return Action::Drop;
   }
@@ -230,7 +230,7 @@ class QPSAction : public DNSAction
 public:
   QPSAction(int limit) : d_qps(limit, limit) 
   {}
-  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
   {
     if(d_qps.check())
       return Action::Allow;
@@ -250,7 +250,7 @@ class DelayAction : public DNSAction
 public:
   DelayAction(int msec) : d_msec(msec)
   {}
-  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
   {
     *ruleresult=std::to_string(d_msec);
     return Action::Delay;
@@ -268,7 +268,7 @@ class PoolAction : public DNSAction
 {
 public:
   PoolAction(const std::string& pool) : d_pool(pool) {}
-  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
   {
     *ruleresult=d_pool;
     return Action::Pool;
@@ -287,7 +287,7 @@ class QPSPoolAction : public DNSAction
 {
 public:
   QPSPoolAction(unsigned int limit, const std::string& pool) : d_qps(limit, limit), d_pool(pool) {}
-  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
   {
     if(d_qps.check()) {
       *ruleresult=d_pool;
@@ -310,7 +310,7 @@ class RCodeAction : public DNSAction
 {
 public:
   RCodeAction(int rcode) : d_rcode(rcode) {}
-  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
   {
     dh->rcode = d_rcode;
     dh->qr = true; // for good measure
@@ -328,7 +328,7 @@ private:
 class TCAction : public DNSAction
 {
 public:
-  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
   {
     dh->tc = true;
     dh->qr = true; // for good measure
@@ -345,58 +345,81 @@ class SpoofAction : public DNSAction
 public:
   SpoofAction(const ComboAddress& a) : d_a(a) { d_aaaa.sin4.sin_family = 0;}
   SpoofAction(const ComboAddress& a, const ComboAddress& aaaa) : d_a(a), d_aaaa(aaaa) {}
-  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+  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
   {
-    if((qtype == QType::A && d_a.sin4.sin_family == 0) ||
-       (qtype == QType::AAAA && d_aaaa.sin4.sin_family == 0) || (qtype != QType::A && qtype != QType::AAAA))
+    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)))
       return Action::None;
 
+    unsigned int consumed=0;
+    uint8_t rdatalen = 0;
+    if (!d_cname.empty()) {
+      qtype = QType::CNAME;
+      rdatalen = d_cname.wirelength();
+    } else {
+      rdatalen = qtype == QType::A ? 4 : 16;
+    }
+
+    const unsigned char recordstart[]={0xc0, 0x0c,    // compressed name
+                                      0, (unsigned char) qtype,
+                                      0, QClass::IN, // IN
+                                      0, 0, 0, 60,   // TTL
+                                      0, rdatalen};
+
+    DNSName ignore((char*)dh, len, sizeof(dnsheader), false, 0, 0, &consumed);
+
+    if (bufferSize < (sizeof(dnsheader) + consumed + 4 + sizeof(recordstart) + rdatalen)) {
+      return Action::None;
+    }
+
     dh->qr = true; // for good measure
     dh->ra = dh->rd; // for good measure
     dh->ad = false;
     dh->ancount = htons(1);
-    dh->arcount = 0; // for now, forget about your EDNS, we're marching over it 
-    unsigned int consumed=0;
-
-    DNSName ignore((char*)dh, len, sizeof(dnsheader), false, 0, 0, &consumed);
+    dh->arcount = 0; // for now, forget about your EDNS, we're marching over it
 
     char* dest = ((char*)dh) +sizeof(dnsheader) + consumed + 4;
-    uint8_t addrlen = qtype == QType::A ? 4 : 16;
-
-    const unsigned char recordstart[]={0xc0, 0x0c,  // compressed name
-                                      0, (unsigned char) qtype,       
-                                      0, 1,        // IN
-                                      0, 0, 0, 60, // TTL
-                                      0, addrlen};       
     memcpy(dest, recordstart, sizeof(recordstart));
     if(qtype==QType::A) 
       memcpy(dest+sizeof(recordstart), &d_a.sin4.sin_addr.s_addr, 4);
-    else
+    else if (qtype==QType::AAAA)
       memcpy(dest+sizeof(recordstart), &d_aaaa.sin6.sin6_addr.s6_addr, 16);
-    len = (dest + sizeof(recordstart) + addrlen) - (char*)dh;
+    else if (qtype==QType::CNAME) {
+      string wireData = d_cname.toDNSString();
+      memcpy(dest+sizeof(recordstart), wireData.c_str(), wireData.length());
+    }
+    len = (dest + sizeof(recordstart) + rdatalen) - (char*)dh;
     return Action::HeaderModify;
   }
   string toString() const override
   {
     string ret;
-    if(d_a.sin4.sin_family)
-      ret="spoof in "+d_a.toString();
-    if(d_aaaa.sin6.sin6_family) {
+    if(!d_cname.empty()) {
       if(!ret.empty()) ret += ", ";
-      ret+="spoof in "+d_aaaa.toString();
+      ret+="spoof in "+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();
+      }
     }
     return ret;
   }
 private:
   ComboAddress d_a;
   ComboAddress d_aaaa;
+  DNSName d_cname;
 };
 
 
 class NoRecurseAction : public DNSAction
 {
 public:
-  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
   {
     dh->rd = false;
     return Action::HeaderModify;
@@ -426,7 +449,7 @@ public:
     if(d_fp)
       fclose(d_fp);
   }
-  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
   {
     if(!d_fp) {
       vinfolog("Packet from %s for %s %s with id %d", remote.toStringWithPort(), qname.toString(), QType(qtype).getName(), dh->id);
@@ -450,7 +473,7 @@ private:
 class DisableValidationAction : public DNSAction
 {
 public:
-  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, string* ruleresult) const override
+  DNSAction::Action operator()(const ComboAddress& remote, const DNSName& qname, uint16_t qtype, dnsheader* dh, uint16_t& len, uint16_t bufferSize, string* ruleresult) const override
   {
     dh->cd = true;
     return Action::HeaderModify;
index e8b7e6484ef1d985176d84933ad41be563d52d79..81be25024c90ee7a929f9ecfa039e31300c4c3db 100644 (file)
@@ -141,12 +141,22 @@ class DNSDistTest(unittest.TestCase):
             else:
                 # unexpected query, or health check
                 response = dns.message.make_response(request)
-                rrset = dns.rrset.from_text(request.question[0].name,
-                                            3600,
-                                            request.question[0].rdclass,
-                                            request.question[0].rdtype,
-                                            '127.0.0.1')
-                response.answer.append(rrset)
+                if request.question[0].rdclass == dns.rdataclass.IN:
+                    if request.question[0].rdtype == dns.rdatatype.A:
+                        rrset = dns.rrset.from_text(request.question[0].name,
+                                                    3600,
+                                                    request.question[0].rdclass,
+                                                    request.question[0].rdtype,
+                                                    '127.0.0.1')
+                    elif request.question[0].rdtype == dns.rdatatype.AAAA:
+                        rrset = dns.rrset.from_text(request.question[0].name,
+                                                    3600,
+                                                    request.question[0].rdclass,
+                                                    request.question[0].rdtype,
+                                                    '::1')
+                if rrset:
+                    response.answer.append(rrset)
+
             sock.sendto(response.to_wire(), addr)
         sock.close()
 
@@ -178,12 +188,21 @@ class DNSDistTest(unittest.TestCase):
             else:
                 # unexpected query, or health check
                 response = dns.message.make_response(request)
-                rrset = dns.rrset.from_text(request.question[0].name,
-                                            3600,
-                                            request.question[0].rdclass,
-                                            request.question[0].rdtype,
-                                            '127.0.0.1')
-                response.answer.append(rrset)
+                if request.question[0].rdclass == dns.rdataclass.IN:
+                    if request.question[0].rdtype == dns.rdatatype.A:
+                        rrset = dns.rrset.from_text(request.question[0].name,
+                                                    3600,
+                                                    request.question[0].rdclass,
+                                                    request.question[0].rdtype,
+                                                    '127.0.0.1')
+                    elif request.question[0].rdtype == dns.rdatatype.AAAA:
+                        rrset = dns.rrset.from_text(request.question[0].name,
+                                                    3600,
+                                                    request.question[0].rdclass,
+                                                    request.question[0].rdtype,
+                                                    '::1')
+                if rrset:
+                    response.answer.append(rrset)
 
             wire = response.to_wire()
             conn.send(struct.pack("!H", len(wire)))
index 662999b18988fd249e76d2634c24c7c1172bbe07..2bf52f8c0d3fa4792154374fcaeeb4340bf87bd1 100644 (file)
@@ -203,6 +203,7 @@ class TestAdvancedSpoof(DNSDistTest):
 
     _config_template = """
     addDomainSpoof("spoof.tests.powerdns.com.", "192.0.2.1", "2001:DB8::1")
+    addDomainCNAMESpoof("cnamespoof.tests.powerdns.com.", "cname.tests.powerdns.com.")
     newServer{address="127.0.0.1:%s"}
     """
 
@@ -260,6 +261,32 @@ class TestAdvancedSpoof(DNSDistTest):
         receivedResponse.id = expectedResponse.id
         self.assertEquals(expectedResponse, receivedResponse)
 
+    def testSpoofCNAME(self):
+        """
+        Send an A query for "cnamespoof.tests.powerdns.com.",
+        check that dnsdist sends a spoofed result.
+        """
+        name = 'cnamespoof.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,
+                                    'cname.tests.powerdns.com.')
+        expectedResponse.answer.append(rrset)
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False, timeout=2.0)
+        self.assertTrue(receivedResponse)
+        receivedResponse.id = expectedResponse.id
+        self.assertEquals(expectedResponse, receivedResponse)
+
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False, timeout=2.0)
+        self.assertTrue(receivedResponse)
+        receivedResponse.id = expectedResponse.id
+        self.assertEquals(expectedResponse, receivedResponse)
 
 class TestAdvancedPoolRouting(DNSDistTest):
 
@@ -424,7 +451,6 @@ class TestAdvancedRoundRobinLBOneDown(DNSDistTest):
             self.assertEquals(query, receivedQuery)
             self.assertEquals(response, receivedResponse)
 
-        print(TestAdvancedRoundRobinLB._responsesCounter)
         total = 0
         for key in TestAdvancedRoundRobinLB._responsesCounter:
             value = TestAdvancedRoundRobinLB._responsesCounter[key]