]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
make dnsdist spoofing actions support multiple A and AAAA records which we'll shuffle...
authorbert hubert <bert.hubert@netherlabs.nl>
Sat, 20 Feb 2016 20:58:41 +0000 (21:58 +0100)
committerbert hubert <bert.hubert@netherlabs.nl>
Sat, 20 Feb 2016 20:58:41 +0000 (21:58 +0100)
pdns/dnsdist-lua.cc
pdns/dnsdist.cc
pdns/dnsdistconf.lua
pdns/dnsrulactions.hh
regression-tests.dnsdist/test_Advanced.py

index 3848d2b975fe8191e58b985102789e7a470f897a..18b7920bbe4935d45a32675fe54e555ed925911a 100644 (file)
@@ -573,37 +573,51 @@ vector<std::function<void(void)>> setupLua(bool client, const std::string& confi
       return std::shared_ptr<DNSAction>(new QPSPoolAction(limit, a));
     });
 
-  g_lua.writeFunction("SpoofAction", [](const string& a, boost::optional<string> b) {
-      if(b) 
-       return std::shared_ptr<DNSAction>(new SpoofAction(ComboAddress(a), ComboAddress(*b)));
-      else 
-       return std::shared_ptr<DNSAction>(new SpoofAction(ComboAddress(a)));
+  g_lua.writeFunction("SpoofAction", [](boost::variant<string,vector<pair<int, string>>> inp, boost::optional<string> b ) {
+      vector<ComboAddress> addrs;
+      if(auto s = boost::get<string>(&inp))
+        addrs.push_back(ComboAddress(*s));
+      else {
+        const auto& v = boost::get<vector<pair<int,string>>>(inp);
+        for(const auto& a: v)
+          addrs.push_back(ComboAddress(a.second));
+      }
+      if(b)
+        addrs.push_back(ComboAddress(*b));
+      return std::shared_ptr<DNSAction>(new SpoofAction(addrs));
     });
 
   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) { 
+  g_lua.writeFunction("addDomainSpoof", [](const std::string& domain, boost::variant<string,vector<pair<int, string>>> inp, boost::optional<string> b) { 
       setLuaSideEffect();
       SuffixMatchNode smn;
-      ComboAddress a, b;
-      b.sin6.sin6_family=0;
+      vector<ComboAddress> outp;
       try
       {
        smn.add(DNSName(domain));
-       a=ComboAddress(ip);
-       if(ip6)
-         b=ComboAddress(*ip6);
+
+        if(auto s = boost::get<string>(&inp))
+          outp.push_back(ComboAddress(*s));
+        else {
+          const auto& v = boost::get<vector<pair<int,string>>>(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<SuffixMatchNodeRule>(smn), 
-               std::make_shared<SpoofAction>(a, b)  });
+               std::make_shared<SpoofAction>(outp)  });
        });
 
     });
index 87cadb55852e8bab8276c423f0c98da3000e8233..7471084a65016370521b72fc15035bbf0e05105d 100644 (file)
@@ -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);
   }
 }
index 10581f89bb02ebe143be9365d6e12fc596b9d8c8..aa549484783a19a622ed2caf181d2603879fdc9b 100644 (file)
@@ -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!"))
 
index bee5b643aadcbac102113b96e434a9853a78636c..8ebb09dc69658f0e76590b0a464e3e5333c400bc 100644 (file)
@@ -473,85 +473,110 @@ public:
 class SpoofAction : public DNSAction
 {
 public:
-  SpoofAction(const ComboAddress& a)
+  SpoofAction(const vector<ComboAddress>& 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<ComboAddress> 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<ComboAddress> d_addrs;
   DNSName d_cname;
 };
 
index 64bda343123dd2abf3aacff5474ee2a1657e70ce..4d5be0123bc1db1154d78ada3c7f09934b597ea4 100644 (file)
@@ -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 = """