]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Support setting the value of AA, AD and RA when spoofing
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 18 Nov 2019 16:14:04 +0000 (17:14 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 20 Jan 2020 09:15:44 +0000 (10:15 +0100)
pdns/dnsdist-console.cc
pdns/dnsdist-lua-actions.cc
pdns/dnsdist-lua.hh
pdns/dnsdist.cc
pdns/dnsdistdist/docs/rules-actions.rst
regression-tests.dnsdist/test_Advanced.py
regression-tests.dnsdist/test_Basics.py
regression-tests.dnsdist/test_DynBlocks.py
regression-tests.dnsdist/test_EDNSSelfGenerated.py

index 76b99967f5b55cfcfe98a53fc115a1a1c2d8a445..6c0f78205dac755d1d325aa8fc42a4621b4620f3 100644 (file)
@@ -569,8 +569,8 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "snmpAgent", true, "enableTraps [, masterSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `masterSocket` an optional string specifying how to connect to the master agent"},
   { "SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"},
   { "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
-  { "SpoofAction", true, "{ip, ...} ", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
-  { "SpoofCNAMEAction", true, "cname", "Forge a response with the specified CNAME value" },
+  { "SpoofAction", true, "ip|list of ips [, options]", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
+  { "SpoofCNAMEAction", true, "cname [, options]", "Forge a response with the specified CNAME value" },
   { "SuffixMatchNodeRule", true, "smn[, quiet]", "Matches based on a group of domain suffixes for rapid testing of membership. Pass true as second parameter to prevent listing of all domains matched" },
   { "TagAction", true, "name, value", "set the tag named 'name' to the given value" },
   { "TagResponseAction", true, "name, value", "set the tag named 'name' to the given value" },
index 0c3ae18a078ed4a9b890089c47d64f44efa3ea7c..a60bd1f77be5b12f9704232dea79df869fcc682e 100644 (file)
@@ -444,8 +444,21 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
   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;
+  if (d_setAA) {
+    dq->dh->aa = *d_setAA;
+  }
+  if (d_setAD) {
+    dq->dh->ad = *d_setAD;
+  }
+  else {
+    dq->dh->ad = false;
+  }
+  if (d_setRA) {
+    dq->dh->ra = *d_setRA;
+  }
+  else {
+    dq->dh->ra = dq->dh->rd; // for good measure
+  }
   dq->dh->ancount = 0;
   dq->dh->arcount = 0; // for now, forget about your EDNS, we're marching over it
 
@@ -1244,6 +1257,23 @@ static void addAction(GlobalStateHolder<vector<T> > *someRulActions, const luadn
     });
 }
 
+typedef std::unordered_map<std::string, boost::variant<bool> > spoofparams_t;
+
+static void parseSpoofConfig(boost::optional<spoofparams_t> vars, boost::optional<bool>& setAA, boost::optional<bool>& setAD, boost::optional<bool>& setRA)
+{
+  if (vars) {
+    if (vars->count("aa")) {
+      setAA = boost::get<bool>((*vars)["aa"]);
+    }
+    if (vars->count("ad")) {
+      setAD = boost::get<bool>((*vars)["ad"]);
+    }
+    if (vars->count("ra")) {
+      setRA = boost::get<bool>((*vars)["ra"]);
+    }
+  }
+}
+
 void setupLuaActions()
 {
   g_lua.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr<DNSAction> action, boost::optional<luaruleparams_t> params) {
@@ -1336,7 +1366,7 @@ void setupLuaActions()
       return std::shared_ptr<DNSAction>(new QPSPoolAction(limit, a));
     });
 
-  g_lua.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<std::string> b ) {
+  g_lua.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<std::string> b, boost::optional<spoofparams_t> vars ) {
       vector<ComboAddress> addrs;
       if(auto s = boost::get<std::string>(&inp))
         addrs.push_back(ComboAddress(*s));
@@ -1345,13 +1375,46 @@ void setupLuaActions()
         for(const auto& a: v)
           addrs.push_back(ComboAddress(a.second));
       }
-      if(b)
+      if(b) {
         addrs.push_back(ComboAddress(*b));
-      return std::shared_ptr<DNSAction>(new SpoofAction(addrs));
+      }
+
+      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(addrs));
+      boost::optional<bool> setAA = boost::none;
+      boost::optional<bool> setAD = boost::none;
+      boost::optional<bool> setRA = boost::none;
+      parseSpoofConfig(vars, setAA, setAD, setRA);
+      auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
+      if (setAA) {
+        sa->setAA(*setAA);
+      }
+      if (setAD) {
+        sa->setAD(*setAD);
+      }
+      if (setRA) {
+        sa->setRA(*setRA);
+      }
+      return ret;
     });
 
-  g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a) {
-      return std::shared_ptr<DNSAction>(new SpoofAction(a));
+  g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a, boost::optional<spoofparams_t> vars) {
+      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(a));
+      boost::optional<bool> setAA = boost::none;
+      boost::optional<bool> setAD = boost::none;
+      boost::optional<bool> setRA = boost::none;
+      parseSpoofConfig(vars, setAA, setAD, setRA);
+      auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
+      if (setAA) {
+        sa->setAA(*setAA);
+      }
+      if (setAD) {
+        sa->setAD(*setAD);
+      }
+      if (setRA) {
+        sa->setRA(*setRA);
+      }
+      return ret;
+
     });
 
   g_lua.writeFunction("DropAction", []() {
index 827d6e1cfb99c78df7ce96b168e1406e1ca56dcb..89f49c0ee905a075e51b0a4cbf7415ee735cd335 100644 (file)
@@ -72,9 +72,27 @@ public:
     }
     return ret;
   }
+
+  void setAA(bool aa)
+  {
+    d_setAA = aa;
+  }
+
+  void setAD(bool ad)
+  {
+    d_setAD = ad;
+  }
+
+  void setRA(bool ra)
+  {
+    d_setRA = ra;
+  }
 private:
   std::vector<ComboAddress> d_addrs;
   DNSName d_cname;
+  boost::optional<bool> d_setAA{boost::none};
+  boost::optional<bool> d_setAD{boost::none};
+  boost::optional<bool> d_setRA{boost::none};
 };
 
 typedef boost::variant<string, vector<pair<int, string>>, std::shared_ptr<DNSRule>, DNSName, vector<pair<int, DNSName> > > luadnsrule_t;
index 5dfd81549b9818c168fd0f209deb2fa5ab22f4d6..96af451cf7f52905ac9b89ef937312c923e6f37f 100644 (file)
@@ -1098,6 +1098,9 @@ bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::s
   case DNSAction::Action::Truncate:
     dq.dh->tc = true;
     dq.dh->qr = true;
+    dq.dh->ra = dq.dh->rd;
+    dq.dh->aa = false;
+    dq.dh->ad = false;
     return true;
     break;
   case DNSAction::Action::HeaderModify:
@@ -1185,6 +1188,9 @@ static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const stru
           vinfolog("Query from %s truncated because of dynamic block", dq.remote->toStringWithPort());
           dq.dh->tc = true;
           dq.dh->qr = true;
+          dq.dh->ra = dq.dh->rd;
+          dq.dh->aa = false;
+          dq.dh->ad = false;
           return true;
         }
         else {
@@ -1240,6 +1246,9 @@ static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const stru
           vinfolog("Query from %s for %s truncated because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toLogString());
           dq.dh->tc = true;
           dq.dh->qr = true;
+          dq.dh->ra = dq.dh->rd;
+          dq.dh->aa = false;
+          dq.dh->ad = false;
           return true;
         }
         else {
index c1d1d10a0aaab85d60c9710886c0941e27619b25..9a2d68aa31b307fab56ba64998f18c4a45f5678b 100644 (file)
@@ -1175,20 +1175,40 @@ The following actions exist.
 
   :param string message: The message to include
 
-.. function:: SpoofAction(ip[, ip[...]])
-              SpoofAction(ips)
+.. function:: SpoofAction(ip [, options])
+              SpoofAction(ips [, options])
+
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
 
   Forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA) addresses.
   If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in.
 
   :param string ip: An IPv4 and/or IPv6 address to spoof
   :param {string} ips: A table of IPv4 and/or IPv6 addresses to spoof
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
 
-.. function:: SpoofCNAMEAction(cname)
+.. function:: SpoofCNAMEAction(cname [, options])
+
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
 
   Forge a response with the specified CNAME value.
 
   :param string cname: The name to respond with
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
 
 .. function:: TagAction(name, value)
 
index bb309b76387b98ce4489863eb5ee6cd35f4ee685..3d9fb5716f9b88a245b497bcbf8afd1c88839b11 100644 (file)
@@ -426,6 +426,8 @@ class TestAdvancedTruncateAnyAndTCP(DNSDistTest):
         """
         name = 'anytruncatetcp.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'ANY', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
 
         response = dns.message.make_response(query)
         rrset = dns.rrset.from_text(name,
@@ -1239,6 +1241,8 @@ class TestAdvancedLuaTruncated(DNSDistTest):
         """
         name = 'tc.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         response = dns.message.make_response(query)
         rrset = dns.rrset.from_text(name,
                                     3600,
index 24cbc549f0b01fb77f9a7ccca0c86be82be83a91..79efaa0fc4dcacff278c6449d6315d3b50114c2d 100644 (file)
@@ -90,6 +90,8 @@ class TestBasics(DNSDistTest):
         """
         name = 'any.tests.powerdns.com.'
         query = dns.message.make_query(name, 'ANY', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.flags |= dns.flags.TC
 
index 542eb218b97e144f78415fb139897bd972449540..fad4a22426e7e233f8248d67410c13cc23c6e656 100644 (file)
@@ -732,6 +732,8 @@ class TestDynBlockQPSActionTruncated(DNSDistTest):
         """
         name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         response = dns.message.make_response(query)
         rrset = dns.rrset.from_text(name,
                                     60,
index fdaeec32a0d368db5718b387d9bff6752bf2d559..25984ecfcfe551790fba88d79347326816590cae 100644 (file)
@@ -43,6 +43,8 @@ class TestEDNSSelfGenerated(DNSDistTest):
 
         name = 'no-edns.tc.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.flags |= dns.flags.TC
 
@@ -95,6 +97,8 @@ class TestEDNSSelfGenerated(DNSDistTest):
 
         name = 'edns-no-do.tc.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query, our_payload=1042)
         expectedResponse.flags |= dns.flags.TC
 
@@ -153,6 +157,8 @@ class TestEDNSSelfGenerated(DNSDistTest):
 
         name = 'edns-do.tc.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=True)
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query, our_payload=1042)
         expectedResponse.flags |= dns.flags.TC
 
@@ -212,6 +218,8 @@ class TestEDNSSelfGenerated(DNSDistTest):
 
         name = 'edns-options.tc.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[ecso], payload=512, want_dnssec=True)
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query, our_payload=1042)
         expectedResponse.flags |= dns.flags.TC
 
@@ -294,6 +302,8 @@ class TestEDNSSelfGeneratedDisabled(DNSDistTest):
 
         name = 'edns-no-do.tc.edns-self-disabled.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.flags |= dns.flags.TC