]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: add EDNSOptionRule
authorPeter van Dijk <peter.van.dijk@powerdns.com>
Tue, 24 Jul 2018 13:17:23 +0000 (15:17 +0200)
committerPeter van Dijk <peter.van.dijk@powerdns.com>
Wed, 8 Aug 2018 12:48:57 +0000 (14:48 +0200)
pdns/dnsdist-console.cc
pdns/dnsdist-ecs.cc
pdns/dnsdist-ecs.hh
pdns/dnsdist-lua-rules.cc
pdns/dnsdistdist/dnsdist-rules.hh
pdns/dnsdistdist/docs/rules-actions.rst
regression-tests.dnsdist/test_Advanced.py

index de432c4175c1d0dfcb7e417524ff290baf4dd477..515cc810447e77b8e5a1915d84f16ab13661c4b1 100644 (file)
@@ -404,6 +404,7 @@ const std::vector<ConsoleKeyword> 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" },
index 05bfd53aa9a2d6b7b5243aec5828fd3ff1c32ced..ff40bd8662f73d6866c53e7575caf30f7bfee76c 100644 (file)
@@ -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<uint8_t>& newContent)
 {
   assert(initialPacket.size() >= sizeof(dnsheader));
index d280ee54ffccc9b55fa35ae8bd2eb4d2dd144efb..fad0b77c495b05872d8a046eb1d733139a0a9b5c 100644 (file)
@@ -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<uint8_t>& 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);
index f65a68c466f1acfd3ba9df5d3e4f4777b7cb9d58..bb501d2b3b76ee5de7d2411e4b2281577724139e 100644 (file)
@@ -417,6 +417,10 @@ void setupLuaRules()
       return std::shared_ptr<DNSRule>(new ERCodeRule(rcode));
     });
 
+  g_lua.writeFunction("EDNSOptionRule", [](uint16_t optcode) {
+      return std::shared_ptr<DNSRule>(new EDNSOptionRule(optcode));
+    });
+
   g_lua.writeFunction("showRules", [](boost::optional<ruleparams_t> vars) {
       showRules(&g_rulactions, vars);
     });
index 4746ce2f2b9e9e63361f1431601a6de2bf3672c8..2a4c64ac29854058f41c7f8c3b1e23a35b7e0d44 100644 (file)
@@ -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<const char*>(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:
index 386feea2df02f999e8cdeb31f53cdf9eb350e6e9..ff0bc8a127b7e6c9d775cb674a4189816005d6f5 100644 (file)
@@ -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
index d65cca8e4b63f1ba39b11ec64d7093ed5f93e71d..5dc77afe423dc225392c4a6d539d8a06365b2a9d 100644 (file)
@@ -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)