From d83feb68718bb021f3bc0e6de604fb642b97b66a Mon Sep 17 00:00:00 2001 From: Chris Hofstaedtler Date: Fri, 5 Jan 2018 18:00:57 +0100 Subject: [PATCH] Add ERCodeRule --- pdns/dns.cc | 5 ++ pdns/dns.hh | 10 ++++ pdns/dnsdist-console.cc | 1 + pdns/dnsdist-lua-rules.cc | 51 +++++++++++++++++++ pdns/dnsdist-lua-vars.cc | 11 +++- pdns/dnsdistdist/docs/reference/constants.rst | 17 +++++++ pdns/dnsdistdist/docs/rules-actions.rst | 15 ++++-- .../test_EdnsClientSubnet.py | 1 + regression-tests.dnsdist/test_Responses.py | 51 +++++++++++++++++++ 9 files changed, 158 insertions(+), 4 deletions(-) diff --git a/pdns/dns.cc b/pdns/dns.cc index 051e27aef..b04a03257 100644 --- a/pdns/dns.cc +++ b/pdns/dns.cc @@ -54,6 +54,7 @@ std::vector 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: diff --git a/pdns/dns.hh b/pdns/dns.hh index 88a658c5a..9f75ba4a4 100644 --- a/pdns/dns.hh +++ b/pdns/dns.hh @@ -29,6 +29,9 @@ #include "dnsname.hh" #include #include + +#undef BADSIG // signal.h SIG_ERR + class DNSBackend; struct DNSRecord; @@ -58,6 +61,13 @@ public: static std::vector 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: diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index 40aa405ea..ef2c8dbde 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -361,6 +361,7 @@ const std::vector 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" }, diff --git a/pdns/dnsdist-lua-rules.cc b/pdns/dnsdist-lua-rules.cc index a995d6772..92c89e41a 100644 --- a/pdns/dnsdist-lua-rules.cc +++ b/pdns/dnsdist-lua-rules.cc @@ -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(new RCodeRule(rcode)); }); + g_lua.writeFunction("ERCodeRule", [](int rcode) { + return std::shared_ptr(new ERCodeRule(rcode)); + }); + g_lua.writeFunction("showRules", []() { setLuaNoSideEffect(); boost::format fmt("%-3d %9d %-50s %s\n"); diff --git a/pdns/dnsdist-lua-vars.cc b/pdns/dnsdist-lua-vars.cc index cbf482e91..76e43058c 100644 --- a/pdns/dnsdist-lua-vars.cc +++ b/pdns/dnsdist-lua-vars.cc @@ -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 > dd; for(const auto& n : QType::names) diff --git a/pdns/dnsdistdist/docs/reference/constants.rst b/pdns/dnsdistdist/docs/reference/constants.rst index b7bdcb187..38b994d8c 100644 --- a/pdns/dnsdistdist/docs/reference/constants.rst +++ b/pdns/dnsdistdist/docs/reference/constants.rst @@ -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: diff --git a/pdns/dnsdistdist/docs/rules-actions.rst b/pdns/dnsdistdist/docs/rules-actions.rst index 726ef951e..32055fc3c 100644 --- a/pdns/dnsdistdist/docs/rules-actions.rst +++ b/pdns/dnsdistdist/docs/rules-actions.rst @@ -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 `. + 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. diff --git a/regression-tests.dnsdist/test_EdnsClientSubnet.py b/regression-tests.dnsdist/test_EdnsClientSubnet.py index 747837f65..d2e0b9b06 100644 --- a/regression-tests.dnsdist/test_EdnsClientSubnet.py +++ b/regression-tests.dnsdist/test_EdnsClientSubnet.py @@ -3,6 +3,7 @@ import dns import clientsubnetoption import cookiesoption from dnsdisttests import DNSDistTest +from datetime import datetime, timedelta class TestEdnsClientSubnetNoOverride(DNSDistTest): """ diff --git a/regression-tests.dnsdist/test_Responses.py b/regression-tests.dnsdist/test_Responses.py index a1f907292..c3df94507 100644 --- a/regression-tests.dnsdist/test_Responses.py +++ b/regression-tests.dnsdist/test_Responses.py @@ -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 = """ -- 2.40.0