From: Peter van Dijk Date: Tue, 24 Jul 2018 13:17:23 +0000 (+0200) Subject: dnsdist: add EDNSOptionRule X-Git-Tag: dnsdist-1.3.3~168^2~2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=4bfdbd34eca7a6d2dab53f19dd9ebeb7df25f9c3;p=thirdparty%2Fpdns.git dnsdist: add EDNSOptionRule --- diff --git a/pdns/dnsdist-console.cc b/pdns/dnsdist-console.cc index de432c4175..515cc81044 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 05bfd53aa9..ff40bd8662 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 d280ee54ff..fad0b77c49 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 f65a68c466..bb501d2b3b 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 4746ce2f2b..2a4c64ac29 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 386feea2df..ff0bc8a127 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 d65cca8e4b..5dc77afe42 100644 --- a/regression-tests.dnsdist/test_Advanced.py +++ b/regression-tests.dnsdist/test_Advanced.py @@ -5,6 +5,7 @@ import os import string import time import dns +import clientsubnetoption from dnsdisttests import DNSDistTest class TestAdvancedAllow(DNSDistTest): @@ -1622,3 +1623,56 @@ class TestAdvancedLuaTempFailureTTL(DNSDistTest): 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)