From 4bfdbd34eca7a6d2dab53f19dd9ebeb7df25f9c3 Mon Sep 17 00:00:00 2001 From: Peter van Dijk Date: Tue, 24 Jul 2018 15:17:23 +0200 Subject: [PATCH 1/3] dnsdist: add EDNSOptionRule --- pdns/dnsdist-console.cc | 1 + pdns/dnsdist-ecs.cc | 31 +++++++++++++ pdns/dnsdist-ecs.hh | 1 + pdns/dnsdist-lua-rules.cc | 4 ++ pdns/dnsdistdist/dnsdist-rules.hh | 39 ++++++++++++++++ pdns/dnsdistdist/docs/rules-actions.rst | 7 +++ regression-tests.dnsdist/test_Advanced.py | 54 +++++++++++++++++++++++ 7 files changed, 137 insertions(+) diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index de432c4175c1..515cc810447e 100644 --- a/pdns/dnsdist-console.cc +++ b/pdns/dnsdist-console.cc @@ -404,6 +404,7 @@ const std::vector g_consoleKeywords{ { "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)" }, + { "EDNSOptionRule", true, "optcode", "matches queries with the specified EDNS0 option present" }, { "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-ecs.cc b/pdns/dnsdist-ecs.cc index 05bfd53aa9a2..ff40bd8662f7 100644 --- a/pdns/dnsdist-ecs.cc +++ b/pdns/dnsdist-ecs.cc @@ -427,6 +427,37 @@ int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optio return 0; } +bool isEDNSOptionInOpt(const std::string& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind) +{ + /* we need at least: + root label (1), type (2), class (2), ttl (4) + rdlen (2)*/ + if (optLen < 11) { + return false; + } + size_t p = optStart + 9; + uint16_t rdLen = (0x100*packet.at(p) + packet.at(p+1)); + p += sizeof(rdLen); + if (11 + rdLen > optLen) { + return false; + } + + size_t rdEnd = p + rdLen; + while ((p + 4) <= rdEnd) { + const uint16_t optionCode = 0x100*packet.at(p) + packet.at(p+1); + p += sizeof(optionCode); + const uint16_t optionLen = 0x100*packet.at(p) + packet.at(p+1); + p += sizeof(optionLen); + if ((p + optionLen) > rdEnd) { + return false; + } + if (optionCode == optionCodeToFind) { + return true; + } + p += optionLen; + } + return false; +} + int rewriteResponseWithoutEDNSOption(const std::string& initialPacket, const uint16_t optionCodeToSkip, vector& newContent) { assert(initialPacket.size() >= sizeof(dnsheader)); diff --git a/pdns/dnsdist-ecs.hh b/pdns/dnsdist-ecs.hh index d280ee54ffcc..fad0b77c495b 100644 --- a/pdns/dnsdist-ecs.hh +++ b/pdns/dnsdist-ecs.hh @@ -28,3 +28,4 @@ void generateOptRR(const std::string& optRData, string& res); int removeEDNSOptionFromOPT(char* optStart, size_t* optLen, const uint16_t optionCodeToRemove); int rewriteResponseWithoutEDNSOption(const std::string& initialPacket, const uint16_t optionCodeToSkip, vector& newContent); int getEDNSOptionsStart(char* packet, const size_t offset, const size_t len, char ** optRDLen, size_t * remaining); +bool isEDNSOptionInOpt(const std::string& packet, const size_t optStart, const size_t optLen, const uint16_t optionCodeToFind); diff --git a/pdns/dnsdist-lua-rules.cc b/pdns/dnsdist-lua-rules.cc index f65a68c466f1..bb501d2b3b76 100644 --- a/pdns/dnsdist-lua-rules.cc +++ b/pdns/dnsdist-lua-rules.cc @@ -417,6 +417,10 @@ void setupLuaRules() return std::shared_ptr(new ERCodeRule(rcode)); }); + g_lua.writeFunction("EDNSOptionRule", [](uint16_t optcode) { + return std::shared_ptr(new EDNSOptionRule(optcode)); + }); + g_lua.writeFunction("showRules", [](boost::optional vars) { showRules(&g_rulactions, vars); }); diff --git a/pdns/dnsdistdist/dnsdist-rules.hh b/pdns/dnsdistdist/dnsdist-rules.hh index 4746ce2f2b9e..2a4c64ac2985 100644 --- a/pdns/dnsdistdist/dnsdist-rules.hh +++ b/pdns/dnsdistdist/dnsdist-rules.hh @@ -882,6 +882,45 @@ private: uint8_t d_extrcode; // upper bits in EDNS0 record }; +class EDNSOptionRule : public DNSRule +{ +public: + EDNSOptionRule(uint16_t optcode) : d_optcode(optcode) + { + } + bool matches(const DNSQuestion* dq) const override + { + uint16_t optStart; + size_t optLen = 0; + bool last = false; + const char * packet = reinterpret_cast(dq->dh); + std::string packetStr(packet, dq->len); + int res = locateEDNSOptRR(packetStr, &optStart, &optLen, &last); + if (res != 0) { + // no EDNS OPT RR + return false; + } + + // root label (1), type (2), class (2), ttl (4) + rdlen (2) + if (optLen < 11) { + return false; + } + + if (optStart < dq->len && packetStr.at(optStart) != 0) { + // OPT RR Name != '.' + return false; + } + + return isEDNSOptionInOpt(packetStr, optStart, optLen, d_optcode); + } + string toString() const override + { + return "ednsoptcode=="+std::to_string(d_optcode); + } +private: + uint16_t d_optcode; +}; + class RDRule : public DNSRule { public: diff --git a/pdns/dnsdistdist/docs/rules-actions.rst b/pdns/dnsdistdist/docs/rules-actions.rst index 386feea2df02..ff0bc8a127b7 100644 --- a/pdns/dnsdistdist/docs/rules-actions.rst +++ b/pdns/dnsdistdist/docs/rules-actions.rst @@ -638,6 +638,13 @@ These ``DNSRule``\ s be one of the following items: :param int rcode: The RCODE to match on +.. function:: EDNSOptionRule(optcode) + + .. versionadded:: 1.4.0 + + Matches queries or responses with the specified EDNS option present. + ``optcode`` is specified as an integer. + .. function:: RDRule() .. versionadded:: 1.2.0 diff --git a/regression-tests.dnsdist/test_Advanced.py b/regression-tests.dnsdist/test_Advanced.py index d65cca8e4b63..5dc77afe423d 100644 --- a/regression-tests.dnsdist/test_Advanced.py +++ b/regression-tests.dnsdist/test_Advanced.py @@ -5,6 +5,7 @@ import string import time import dns +import clientsubnetoption from dnsdisttests import DNSDistTest class TestAdvancedAllow(DNSDistTest): @@ -1622,3 +1623,56 @@ def testTempFailureTTLBinding(self): receivedQuery.id = query.id self.assertEquals(query, receivedQuery) self.assertEquals(receivedResponse, response) + +class TestAdvancedEDNSOptionRule(DNSDistTest): + + _config_template = """ + newServer{address="127.0.0.1:%s"} + addAction(EDNSOptionRule(8), DropAction()) + """ + + def testDropped(self): + """ + A question with ECS is dropped + """ + + name = 'ednsoptionrule.advanced.tests.powerdns.com.' + + ecso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24) + query = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[ecso], payload=512) + + (_, receivedResponse) = self.sendUDPQuery(query, response=None) + self.assertEquals(receivedResponse, None) + (_, receivedResponse) = self.sendTCPQuery(query, response=None) + self.assertEquals(receivedResponse, None) + + def testReplied(self): + """ + A question without ECS is answered + """ + + name = 'ednsoptionrule.advanced.tests.powerdns.com.' + + # both with EDNS + query = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[], payload=512) + response = dns.message.make_response(query) + + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(receivedResponse, response) + + # and with no EDNS at all + query = dns.message.make_query(name, 'A', 'IN', use_edns=False) + response = dns.message.make_response(query) + + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(receivedResponse, response) From 6bb92a0689745901a92c6a476144caea94568688 Mon Sep 17 00:00:00 2001 From: Peter van Dijk Date: Tue, 7 Aug 2018 15:45:00 +0200 Subject: [PATCH 2/3] add EDNSOptionCode table --- pdns/dnsdist-lua-vars.cc | 14 +++++++++++++ pdns/dnsdistdist/docs/reference/constants.rst | 20 +++++++++++++++++++ pdns/dnsdistdist/docs/rules-actions.rst | 2 +- regression-tests.dnsdist/test_Advanced.py | 2 +- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/pdns/dnsdist-lua-vars.cc b/pdns/dnsdist-lua-vars.cc index 77b37fb77b41..5f3a98041007 100644 --- a/pdns/dnsdist-lua-vars.cc +++ b/pdns/dnsdist-lua-vars.cc @@ -69,6 +69,20 @@ void setupLuaVars() {"Additional",3 } }); + g_lua.writeVariable("EDNSOptionCode", std::unordered_map{ + {"NSID", 3 }, + {"DAU", 5 }, + {"DHU", 6 }, + {"N3U", 7 }, + {"ECS", 8 }, + {"EXPIRE", 9 }, + {"COOKIE", 10 }, + {"TCPKEEPALIVE", 11 }, + {"PADDING", 12 }, + {"CHAIN", 13 }, + {"KEYTAG", 14 } + }); + vector > rcodes = {{"NOERROR", RCode::NoError }, {"FORMERR", RCode::FormErr }, {"SERVFAIL", RCode::ServFail }, diff --git a/pdns/dnsdistdist/docs/reference/constants.rst b/pdns/dnsdistdist/docs/reference/constants.rst index 981655d0a938..147663939897 100644 --- a/pdns/dnsdistdist/docs/reference/constants.rst +++ b/pdns/dnsdistdist/docs/reference/constants.rst @@ -58,6 +58,26 @@ RCodes below and including ``BADVERS`` are extended RCodes that can only be matc Reference: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 + +.. _EDNSOptionCode: + +EDNSOptionCode +-------------- + +- ``EDNSOptionCode.DHU`` +- ``EDNSOptionCode.ECS`` +- ``EDNSOptionCode.N3U`` +- ``EDNSOptionCode.DAU`` +- ``EDNSOptionCode.TCPKEEPALIVE`` +- ``EDNSOptionCode.COOKIE`` +- ``EDNSOptionCode.PADDING`` +- ``EDNSOptionCode.KEYTAG`` +- ``EDNSOptionCode.NSID`` +- ``EDNSOptionCode.CHAIN`` +- ``EDNSOptionCode.EXPIRE`` + +Reference: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11 + .. _DNSSection: DNS Section diff --git a/pdns/dnsdistdist/docs/rules-actions.rst b/pdns/dnsdistdist/docs/rules-actions.rst index ff0bc8a127b7..1d9aa3fd94e9 100644 --- a/pdns/dnsdistdist/docs/rules-actions.rst +++ b/pdns/dnsdistdist/docs/rules-actions.rst @@ -643,7 +643,7 @@ These ``DNSRule``\ s be one of the following items: .. versionadded:: 1.4.0 Matches queries or responses with the specified EDNS option present. - ``optcode`` is specified as an integer. + ``optcode`` is specified as an integer, or a constant such as `EDNSOptionCode.ECS`. .. function:: RDRule() diff --git a/regression-tests.dnsdist/test_Advanced.py b/regression-tests.dnsdist/test_Advanced.py index 5dc77afe423d..d04a44517380 100644 --- a/regression-tests.dnsdist/test_Advanced.py +++ b/regression-tests.dnsdist/test_Advanced.py @@ -1628,7 +1628,7 @@ class TestAdvancedEDNSOptionRule(DNSDistTest): _config_template = """ newServer{address="127.0.0.1:%s"} - addAction(EDNSOptionRule(8), DropAction()) + addAction(EDNSOptionRule(EDNSOptionCode.ECS), DropAction()) """ def testDropped(self): From a98a610850f619589a33c7d1cd22acac6a6d7a3a Mon Sep 17 00:00:00 2001 From: Peter van Dijk Date: Wed, 8 Aug 2018 15:55:00 +0200 Subject: [PATCH 3/3] use EDNSOptionCode constants --- pdns/dnsdist-lua-vars.cc | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/pdns/dnsdist-lua-vars.cc b/pdns/dnsdist-lua-vars.cc index 5f3a98041007..7fe5afdd3917 100644 --- a/pdns/dnsdist-lua-vars.cc +++ b/pdns/dnsdist-lua-vars.cc @@ -20,6 +20,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "dnsdist.hh" +#include "ednsoptions.hh" void setupLuaVars() { @@ -70,17 +71,17 @@ void setupLuaVars() }); g_lua.writeVariable("EDNSOptionCode", std::unordered_map{ - {"NSID", 3 }, - {"DAU", 5 }, - {"DHU", 6 }, - {"N3U", 7 }, - {"ECS", 8 }, - {"EXPIRE", 9 }, - {"COOKIE", 10 }, - {"TCPKEEPALIVE", 11 }, - {"PADDING", 12 }, - {"CHAIN", 13 }, - {"KEYTAG", 14 } + {"NSID", EDNSOptionCode::NSID }, + {"DAU", EDNSOptionCode::DAU }, + {"DHU", EDNSOptionCode::DHU }, + {"N3U", EDNSOptionCode::N3U }, + {"ECS", EDNSOptionCode::ECS }, + {"EXPIRE", EDNSOptionCode::EXPIRE }, + {"COOKIE", EDNSOptionCode::COOKIE }, + {"TCPKEEPALIVE", EDNSOptionCode::TCPKEEPALIVE }, + {"PADDING", EDNSOptionCode::PADDING }, + {"CHAIN", EDNSOptionCode::CHAIN }, + {"KEYTAG", EDNSOptionCode::KEYTAG } }); vector > rcodes = {{"NOERROR", RCode::NoError },