]> granicus.if.org Git - pdns/commitdiff
Add ERCodeRule
authorChris Hofstaedtler <chris.hofstaedtler@deduktiva.com>
Fri, 5 Jan 2018 17:00:57 +0000 (18:00 +0100)
committerChris Hofstaedtler <chris.hofstaedtler@deduktiva.com>
Fri, 5 Jan 2018 17:13:29 +0000 (18:13 +0100)
pdns/dns.cc
pdns/dns.hh
pdns/dnsdist-console.cc
pdns/dnsdist-lua-rules.cc
pdns/dnsdist-lua-vars.cc
pdns/dnsdistdist/docs/reference/constants.rst
pdns/dnsdistdist/docs/rules-actions.rst
regression-tests.dnsdist/test_EdnsClientSubnet.py
regression-tests.dnsdist/test_Responses.py

index 051e27aef78a58c4104e3c8fbfe1d7e1b191d1c0..b04a03257ed18999edbd0a6452845b53c6323ed2 100644 (file)
@@ -54,6 +54,7 @@ std::vector<std::string> RCode::rcodes_s = boost::assign::list_of
   ("Duplicate key name")
   ("Algorithm not supported")
   ("Bad Truncation")
+  ("Bad/missing Server Cookie")
 ;
 
 std::string RCode::to_s(unsigned short rcode) {
@@ -62,6 +63,10 @@ std::string RCode::to_s(unsigned short rcode) {
   return RCode::rcodes_s[rcode];
 }
 
+std::string ERCode::to_s(unsigned short rcode) {
+  return RCode::to_s(rcode);
+}
+
 class BoundsCheckingPointer
 {
 public:
index 88a658c5a52ff384a06382bbd4b7998d101d2b78..9f75ba4a4ac1609fc35c81743b6161366449f837 100644 (file)
@@ -29,6 +29,9 @@
 #include "dnsname.hh"
 #include <time.h>
 #include <sys/types.h>
+
+#undef BADSIG  // signal.h SIG_ERR
+
 class DNSBackend;
 struct DNSRecord;
 
@@ -58,6 +61,13 @@ public:
   static std::vector<std::string> rcodes_s;
 };
 
+class ERCode
+{
+public:
+  enum rcodes_ { BADVERS=16, BADSIG=16, BADKEY=17, BADTIME=18, BADMODE=19, BADNAME=20, BADALG=21, BADTRUNC=22, BADCOOKIE=23 };
+  static std::string to_s(unsigned short rcode);
+};
+
 class Opcode
 {
 public:
index 40aa405eaf3894b63598247af3d4fcf4c601c651..ef2c8dbde458a8df59a15a3a294331c42f778f56 100644 (file)
@@ -361,6 +361,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "QNameWireLengthRule", true, "min, max", "matches if the qname's length on the wire is less than `min` or more than `max` bytes" },
   { "QTypeRule", true, "qtype", "matches queries with the specified qtype" },
   { "RCodeRule", true, "rcode", "matches responses with the specified rcode" },
+  { "ERCodeRule", true, "rcode", "matches responses with the specified extended rcode (EDNS0)" },
   { "sendCustomTrap", true, "str", "send a custom `SNMP` trap from Lua, containing the `str` string"},
   { "setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us" },
   { "setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API" },
index a995d67726723c5fd7e6f175fdc78ad975cbd803..92c89e41a8ce3ffebcd2942b9d37deda52759f88 100644 (file)
@@ -20,6 +20,7 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #include "dnsdist.hh"
+#include "dnsdist-ecs.hh"
 #include "dnsdist-lua.hh"
 
 #include "dnsparser.hh"
@@ -738,6 +739,52 @@ private:
   int d_rcode;
 };
 
+class ERCodeRule : public DNSRule
+{
+public:
+  ERCodeRule(int rcode) : d_rcode(rcode & 0xF), d_extrcode(rcode >> 4)
+  {
+  }
+  bool matches(const DNSQuestion* dq) const override
+  {
+    // avoid parsing EDNS OPT RR when not needed.
+    if (d_rcode != dq->dh->rcode) {
+      return false;
+    }
+
+    char * optStart = NULL;
+    size_t optLen = 0;
+    bool last = false;
+    int res = locateEDNSOptRR((char*)dq->dh, dq->len, &optStart, &optLen, &last);
+    if (res != 0) {
+      // no EDNS OPT RR
+      return d_extrcode == 0;
+    }
+
+    /* root label (1), type (2), class (2), ttl (4) + rdlen (2)*/
+    if (optLen < 11) {
+      return false;
+    }
+
+    if (*optStart != 0) {
+      // OPT RR Name != '.'
+      return false;
+    }
+    EDNS0Record edns0;
+    static_assert(sizeof(EDNS0Record) == sizeof(uint32_t), "sizeof(EDNS0Record) must match sizeof(uint32_t) AKA RR TTL size");
+    memcpy(&edns0, optStart + 5, sizeof edns0);
+
+    return d_extrcode == edns0.extRCode;
+  }
+  string toString() const override
+  {
+    return "ercode=="+ERCode::to_s(d_rcode | (d_extrcode << 4));
+  }
+private:
+  int d_rcode;     // plain DNS Rcode
+  int d_extrcode;  // upper bits in EDNS0 record
+};
+
 class RDRule : public DNSRule
 {
 public:
@@ -1164,6 +1211,10 @@ void setupLuaRules()
       return std::shared_ptr<DNSRule>(new RCodeRule(rcode));
     });
 
+  g_lua.writeFunction("ERCodeRule", [](int rcode) {
+      return std::shared_ptr<DNSRule>(new ERCodeRule(rcode));
+    });
+
   g_lua.writeFunction("showRules", []() {
      setLuaNoSideEffect();
      boost::format fmt("%-3d %9d %-50s %s\n");
index cbf482e916e3e92b923976c79804e26ace4d4646..76e43058c7337da6ad3d26891a2bf0d84eaf768d 100644 (file)
@@ -75,7 +75,16 @@ void setupLuaVars()
                                        {"YXRRSET",  RCode::YXRRSet  },
                                        {"NXRRSET",  RCode::NXRRSet  },
                                        {"NOTAUTH",  RCode::NotAuth  },
-                                       {"NOTZONE",  RCode::NotZone  }
+                                       {"NOTZONE",  RCode::NotZone  },
+                                       {"BADVERS",  ERCode::BADVERS },
+                                       {"BADSIG",   ERCode::BADSIG  },
+                                       {"BADKEY",   ERCode::BADKEY  },
+                                       {"BADTIME",  ERCode::BADTIME   },
+                                       {"BADMODE",  ERCode::BADMODE   },
+                                       {"BADNAME",  ERCode::BADNAME   },
+                                       {"BADALG",   ERCode::BADALG    },
+                                       {"BADTRUNC", ERCode::BADTRUNC  },
+                                       {"BADCOOKIE",ERCode::BADCOOKIE },
   };
   vector<pair<string, int> > dd;
   for(const auto& n : QType::names)
index b7bdcb187c6a7861cd3b271b6c2005c4dc743a3c..38b994d8cc99a684e08d679b29d0a8755e83c55d 100644 (file)
@@ -14,6 +14,8 @@ OPCode
 - ``DNSOpcode.Notify``
 - ``DNSOpcode.Update``
 
+Reference: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-5
+
 .. _DNSQClass:
 
 QClass
@@ -24,6 +26,8 @@ QClass
 - ``QClass.NONE``
 - ``QClass.ANY``
 
+Reference: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2
+
 .. _DNSRCode:
 
 RCode
@@ -40,6 +44,19 @@ RCode
 - ``dnsdist.NXRRSET``
 - ``dnsdist.NOTAUTH``
 - ``dnsdist.NOTZONE``
+- ``dnsdist.BADVERS``
+- ``dnsdist.BADSIG``
+- ``dnsdist.BADKEY``
+- ``dnsdist.BADTIME``
+- ``dnsdist.BADMODE``
+- ``dnsdist.BADNAME``
+- ``dnsdist.BADALG``
+- ``dnsdist.BADTRUNC``
+- ``dnsdist.BADCOOKIE``
+
+RCodes below and including ``BADVERS`` are extended RCodes that can only be matched using :func:`ERCodeRule`.
+
+Reference: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6
 
 .. _DNSSection:
 
index 726ef951e81e64ae6ddaafc272ffd25702a85e49..32055fc3c883ff3d5c79ec02ebd93aeb76bc92da 100644 (file)
@@ -462,8 +462,17 @@ These ``DNSRule``\ s be one of the following items:
 
 .. function:: RCodeRule(rcode)
 
-  Matches queries or responses the specified ``rcode``.
-  ``rcode`` can be specified as an integer or as one of the built-in `RCode <DNSRcode>`.
+  Matches queries or responses with the specified ``rcode``.
+  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
+  Only the non-extended RCode is matched (lower 4bits).
+
+  :param int rcode: The RCODE to match on
+
+.. function:: ERCodeRule(rcode)
+
+  Matches queries or responses with the specified ``rcode``.
+  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
+  The full 16bit RCode will be matched. If no EDNS OPT RR is present, the upper 12 bits are treated as 0.
 
   :param int rcode: The RCODE to match on
 
@@ -709,7 +718,7 @@ The following actions exist.
 .. function:: RCodeAction(rcode)
 
   Reply immediatly by turning the query into a response with the specified ``rcode``.
-  ``rcode`` can be specified as an integer or as one of the built-in `RCode <#rcode>`_.
+  ``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
 
   :param int rcode: The RCODE to respond with.
 
index 747837f65ff5cbbc5f8f726a8af5e85c51d8e175..d2e0b9b063f7a0d47e10ac3073c615f7a0f70cae 100644 (file)
@@ -3,6 +3,7 @@ import dns
 import clientsubnetoption
 import cookiesoption
 from dnsdisttests import DNSDistTest
+from datetime import datetime, timedelta
 
 class TestEdnsClientSubnetNoOverride(DNSDistTest):
     """
index a1f9072929c01b92beb2b17ef8317f88e963c224..c3df9450749588ff9627b6ef14fa348cbf1e33b3 100644 (file)
@@ -53,6 +53,57 @@ class TestResponseRuleNXDelayed(DNSDistTest):
         self.assertEquals(response, receivedResponse)
         self.assertTrue((end - begin) < timedelta(0, 1))
 
+class TestResponseRuleERCode(DNSDistTest):
+
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    addResponseAction(ERCodeRule(dnsdist.BADVERS), DelayResponseAction(1000))
+    """
+
+    def testBADVERSDelayed(self):
+        """
+        Responses: Delayed on BADVERS
+
+        Send an A query to "delayed.responses.tests.powerdns.com.",
+        check that the response delay is longer than 1000 ms
+        for a BADVERS response over UDP, shorter for BADKEY and NoError.
+        """
+        name = 'delayed.responses.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        response.use_edns(edns=True)
+
+        # BADVERS over UDP
+        # BADVERS == 16, so rcode==0, ercode==1
+        response.set_rcode(dns.rcode.BADVERS)
+        begin = datetime.now()
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        end = datetime.now()
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+        self.assertTrue((end - begin) > timedelta(0, 1))
+
+        # BADKEY (17, an ERCode) over UDP
+        response.set_rcode(17)
+        begin = datetime.now()
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        end = datetime.now()
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+        self.assertTrue((end - begin) < timedelta(0, 1))
+
+        # NoError (non-ERcode, basic RCode bits match BADVERS) over UDP
+        response.set_rcode(dns.rcode.NOERROR)
+        begin = datetime.now()
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        end = datetime.now()
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+        self.assertTrue((end - begin) < timedelta(0, 1))
+
 class TestResponseRuleQNameDropped(DNSDistTest):
 
     _config_template = """