]> granicus.if.org Git - pdns/commitdiff
dnsdist: Support for plaintext KVS lookups, suffix matching limits
authorRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 13 Aug 2019 13:44:31 +0000 (15:44 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 13 Aug 2019 13:44:31 +0000 (15:44 +0200)
pdns/dnsdist-lua-bindings.cc
pdns/dnsdistdist/dnsdist-kvs.cc
pdns/dnsdistdist/dnsdist-kvs.hh
pdns/dnsdistdist/docs/reference/kvs.rst
pdns/dnsdistdist/test-dnsdistkvs_cc.cc
regression-tests.dnsdist/test_LMDB.py

index 0b005d74eccf43d4999fcbb4330d49e79c192133..fc51198b0c6bc2bd92bd34c8e4f4a3c3cf608691 100644 (file)
@@ -719,11 +719,11 @@ void setupLuaBindings(bool client)
   g_lua.writeFunction("KeyValueLookupKeySourceIP", []() {
     return std::shared_ptr<KeyValueLookupKey>(new KeyValueLookupKeySourceIP());
   });
-  g_lua.writeFunction("KeyValueLookupKeyQName", []() {
-    return std::shared_ptr<KeyValueLookupKey>(new KeyValueLookupKeyQName());
+  g_lua.writeFunction("KeyValueLookupKeyQName", [](boost::optional<bool> wireFormat) {
+    return std::shared_ptr<KeyValueLookupKey>(new KeyValueLookupKeyQName(wireFormat ? *wireFormat : true));
   });
-  g_lua.writeFunction("KeyValueLookupKeySuffix", []() {
-    return std::shared_ptr<KeyValueLookupKey>(new KeyValueLookupKeySuffix());
+  g_lua.writeFunction("KeyValueLookupKeySuffix", [](boost::optional<size_t> minLabels, boost::optional<bool> wireFormat) {
+    return std::shared_ptr<KeyValueLookupKey>(new KeyValueLookupKeySuffix(minLabels ? *minLabels : 0, wireFormat ? *wireFormat : true));
   });
   g_lua.writeFunction("KeyValueLookupKeyTag", [](const std::string& tag) {
     return std::shared_ptr<KeyValueLookupKey>(new KeyValueLookupKeyTag(tag));
@@ -747,7 +747,7 @@ void setupLuaBindings(bool client)
   });
 #endif /* HAVE_CDB */
 
-  g_lua.registerFunction<std::string(std::shared_ptr<KeyValueStore>::*)(const boost::variant<ComboAddress, DNSName, std::string>)>("lookup", [](std::shared_ptr<KeyValueStore>& kvs, const boost::variant<ComboAddress, DNSName, std::string> keyVar) {
+  g_lua.registerFunction<std::string(std::shared_ptr<KeyValueStore>::*)(const boost::variant<ComboAddress, DNSName, std::string>, boost::optional<bool> wireFormat)>("lookup", [](std::shared_ptr<KeyValueStore>& kvs, const boost::variant<ComboAddress, DNSName, std::string> keyVar, boost::optional<bool> wireFormat) {
     std::string result;
     if (!kvs) {
       return result;
@@ -764,7 +764,7 @@ void setupLuaBindings(bool client)
     }
     else if (keyVar.type() == typeid(DNSName)) {
       DNSName dn = *boost::get<DNSName>(&keyVar);
-      KeyValueLookupKeyQName lookup;
+      KeyValueLookupKeyQName lookup(wireFormat ? *wireFormat : true);
       for (const auto& key : lookup.getKeys(dn)) {
         if (kvs->getValue(key, result)) {
           return result;
@@ -779,13 +779,13 @@ void setupLuaBindings(bool client)
     return result;
   });
 
-  g_lua.registerFunction<std::string(std::shared_ptr<KeyValueStore>::*)(const DNSName&)>("lookupSuffix", [](std::shared_ptr<KeyValueStore>& kvs, const DNSName& dn) {
+  g_lua.registerFunction<std::string(std::shared_ptr<KeyValueStore>::*)(const DNSName&, boost::optional<size_t> minLabels, boost::optional<bool> wireFormat)>("lookupSuffix", [](std::shared_ptr<KeyValueStore>& kvs, const DNSName& dn, boost::optional<size_t> minLabels, boost::optional<bool> wireFormat) {
     std::string result;
     if (!kvs) {
       return result;
     }
 
-    KeyValueLookupKeySuffix lookup;
+    KeyValueLookupKeySuffix lookup(minLabels ? *minLabels : 0, wireFormat ? *wireFormat : true);
     for (const auto& key : lookup.getKeys(dn)) {
       if (kvs->getValue(key, result)) {
         return result;
index a1555f441f5a981fcf7c04d3bd1531a5cc8729f6..b0511140fd3d5c15ad7a82628bc1bca53c169635 100644 (file)
@@ -46,12 +46,21 @@ std::vector<std::string> KeyValueLookupKeySuffix::getKeys(const DNSName& qname)
   }
 
   auto lowerQName = qname.makeLowerCase();
+  size_t labelsCount = lowerQName.countLabels();
+  if (d_minLabels != 0) {
+    if (labelsCount < d_minLabels) {
+      return {};
+    }
+    labelsCount -= (d_minLabels - 1);
+  }
+
   std::vector<std::string> result;
-  result.reserve(lowerQName.countLabels() - 1);
+  result.reserve(labelsCount);
 
   while(!lowerQName.isRoot()) {
-    result.emplace_back(lowerQName.toDNSString());
-    if (!lowerQName.chopOff()) {
+    result.emplace_back(d_wireFormat ? lowerQName.toDNSString() : lowerQName.toString());
+    labelsCount--;
+    if (!lowerQName.chopOff() || labelsCount == 0) {
       break;
     }
   }
index 6efb73f77c2b15107cdf3af5f99d7e90ae3bed26..916bb64c554d493d8f12e278df4bd4d3b0d1440f 100644 (file)
@@ -53,9 +53,16 @@ class KeyValueLookupKeyQName: public KeyValueLookupKey
 {
 public:
 
+  KeyValueLookupKeyQName(bool wireFormat): d_wireFormat(wireFormat)
+  {
+  }
+
   std::vector<std::string> getKeys(const DNSName& qname)
   {
-    return {qname.toDNSStringLC()};
+    if (d_wireFormat) {
+      return {qname.toDNSStringLC()};
+    }
+    return {qname.makeLowerCase().toString()};
   }
 
   std::vector<std::string> getKeys(const DNSQuestion& dq) override
@@ -65,13 +72,23 @@ public:
 
   std::string toString() const override
   {
+    if (d_wireFormat) {
+      return "qname in wire format";
+    }
     return "qname";
   }
+
+private:
+  bool d_wireFormat;
 };
 
 class KeyValueLookupKeySuffix: public KeyValueLookupKey
 {
 public:
+  KeyValueLookupKeySuffix(size_t minLabels, bool wireFormat): d_minLabels(minLabels), d_wireFormat(wireFormat)
+  {
+  }
+
   std::vector<std::string> getKeys(const DNSName& qname);
 
   std::vector<std::string> getKeys(const DNSQuestion& dq) override
@@ -81,8 +98,15 @@ public:
 
   std::string toString() const override
   {
-    return "suffix";
+    if (d_minLabels > 0) {
+      return "suffix " + std::string(d_wireFormat ? "in wire format " : "") + "at least " + std::to_string(d_minLabels) + " labels)";
+    }
+    return "suffix" + std::string(d_wireFormat ? " in wire format" : "");
   }
+
+private:
+  size_t d_minLabels;
+  bool d_wireFormat;
 };
 
 class KeyValueLookupKeyTag: public KeyValueLookupKey
index 00585f53b85597689752999b3f07696019034d4e..5617b26de2df87169087c7660150b288104ae60f 100644 (file)
@@ -43,38 +43,44 @@ If the value found in the LMDB database for the key '\8powerdns\3com\0' was 'thi
 
   Represents a Key Value Store
 
-  .. method:: KeyValueStore:lookup(key)
+  .. method:: KeyValueStore:lookup(key [, wireFormat])
 
     Does a lookup into the corresponding key value store, and return the result as a string.
     The key can be a :class:`ComboAddress` obtained via the :func:`newCA`, a :class:`DNSName` obtained via the :func:`newDNSName` function, or a raw string.
 
     :param ComboAddress, DNSName or string key: The key to look up
+    :param bool wireFormat: If the key is DNSName, whether to use to do the lookup in wire format (default) or in plain text
 
-  .. method:: KeyValueStore:lookupSuffix(key)
+  .. method:: KeyValueStore:lookupSuffix(key [, minLabels [, wireFormat]])
 
     Does a suffix-based lookup into the corresponding key value store, and return the result as a string.
     The key should be a :class:`DNSName` object obtained via the :func:`newDNSName` function, and several lookups will be done, removing one label from the name at a time until a match has been found or there is no label left.
+    If ``minLabels`` is set to a value larger than 0 the lookup will only be done as long as there is at least ``minLabels`` remaining. For example if the initial domain is "sub.powerdns.com." and ``minLabels`` is set to 2, lookups will only be done for "sub.powerdns.com." and "powerdns.com.".
 
     :param DNSName key: The name to look up
+    :param int minLabels: The minimum number of labels to do a lookup for. Default is 0 which means unlimited
+    :param bool wireFormat: Whether to do the lookup in wire format (default) or in plain text
 
   .. method:: KeyValueStore:reload()
 
     Reload the database if this is supported by the underlying store. As of 1.5.0, only CDB stores can be reloaded, and this method is a no-op for LMDB stores.
 
 
-.. function:: KeyValueLookupKeyQName() -> KeyValueLookupKey
+.. function:: KeyValueLookupKeyQName([wireFormat]) -> KeyValueLookupKey
 
   .. versionadded:: 1.5.0
 
   Return a new KeyValueLookupKey object that, when passed to :func:`KeyValueStoreLookupAction`, will return the qname of the query in DNS wire format.
 
+  :param bool wireFormat: Whether to do the lookup in wire format (default) or in plain text
+
 .. function:: KeyValueLookupKeySourceIP() -> KeyValueLookupKey
 
   .. versionadded:: 1.5.0
 
   Return a new KeyValueLookupKey object that, when passed to :func:`KeyValueStoreLookupAction`, will return the source IP of the client in network byte-order.
 
-.. function:: KeyValueLookupKeySuffix() -> KeyValueLookupKey
+.. function:: KeyValueLookupKeySuffix([minLabels [, wireFormat]]) -> KeyValueLookupKey
 
   .. versionadded:: 1.5.0
 
@@ -87,6 +93,15 @@ If the value found in the LMDB database for the key '\8powerdns\3com\0' was 'thi
    * \3com\0
    * \0
 
+  If ``minLabels`` is set to a value larger than 0 the lookup will only be done as long as there is at least ``minLabels`` remaining. Taking back our previous example, it means only the following keys will be returned if ``minLabels`` is set to 2;
+
+   * \3sub\6domain\8powerdns\3com\0
+   * \6domain\8powerdns\3com\0
+   * \8powerdns\3com\0
+
+  :param int minLabels: The minimum number of labels to do a lookup for. Default is 0 which means unlimited
+  :param bool wireFormat: Whether to do the lookup in wire format (default) or in plain text
+
 .. function:: KeyValueLookupKeyTag() -> KeyValueLookupKey
 
   .. versionadded:: 1.5.0
index 7e43eb6a34e20cca09a6abe209597d73d7a50a87..a999ee3f368f8a896c2c0d88017a346044d81346 100644 (file)
@@ -6,7 +6,7 @@
 
 #include "dnsdist-kvs.hh"
 
-static void doKVSChecks(std::unique_ptr<KeyValueStore>& kvs, const ComboAddress& lc, const ComboAddress& rem, const DNSQuestion& dq)
+static void doKVSChecks(std::unique_ptr<KeyValueStore>& kvs, const ComboAddress& lc, const ComboAddress& rem, const DNSQuestion& dq, const DNSName& plaintextDomain)
 {
   /* source IP */
   {
@@ -24,10 +24,10 @@ static void doKVSChecks(std::unique_ptr<KeyValueStore>& kvs, const ComboAddress&
   const DNSName subdomain = DNSName("sub") + *dq.qname;
   const DNSName notPDNS("not-powerdns.com.");
 
-  /* qname */
+  /* qname match, in wire format */
   {
     std::string value;
-    auto lookupKey = make_unique<KeyValueLookupKeyQName>();
+    auto lookupKey = make_unique<KeyValueLookupKeyQName>(true);
     auto keys = lookupKey->getKeys(dq);
     BOOST_CHECK_EQUAL(keys.size(), 1);
     for (const auto& key : keys) {
@@ -51,11 +51,56 @@ static void doKVSChecks(std::unique_ptr<KeyValueStore>& kvs, const ComboAddress&
       value.clear();
       BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
     }
+
+    /* this domain was inserted in plaintext, the wire format lookup should not match */
+    keys = lookupKey->getKeys(plaintextDomain);
+    BOOST_CHECK_EQUAL(keys.size(), 1);
+    for (const auto& key : keys) {
+      value.clear();
+      BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+    }
   }
 
-  /* suffix match */
+  /* qname match, in plain text */
   {
-    auto lookupKey = make_unique<KeyValueLookupKeySuffix>();
+    std::string value;
+    auto lookupKey = make_unique<KeyValueLookupKeyQName>(false);
+    auto keys = lookupKey->getKeys(dq);
+    BOOST_CHECK_EQUAL(keys.size(), 1);
+    for (const auto& key : keys) {
+      value.clear();
+      BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+    }
+
+    /* other domain, should not match */
+    keys = lookupKey->getKeys(notPDNS);
+    BOOST_CHECK_EQUAL(keys.size(), 1);
+    for (const auto& key : keys) {
+      value.clear();
+      BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+    }
+
+    /* subdomain, should not match */
+    keys = lookupKey->getKeys(subdomain);
+    BOOST_CHECK_EQUAL(keys.size(), 1);
+    for (const auto& key : keys) {
+      value.clear();
+      BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+    }
+
+    /* this domain was inserted in plaintext, so it should match */
+    keys = lookupKey->getKeys(plaintextDomain);
+    BOOST_CHECK_EQUAL(keys.size(), 1);
+    for (const auto& key : keys) {
+      value.clear();
+      BOOST_CHECK_EQUAL(kvs->getValue(key, value), true);
+      BOOST_CHECK_EQUAL(value, "this is the value for the plaintext domain");
+    }
+  }
+
+  /* suffix match in wire format */
+  {
+    auto lookupKey = make_unique<KeyValueLookupKeySuffix>(0, true);
     auto keys = lookupKey->getKeys(dq);
     BOOST_CHECK_EQUAL(keys.size(), dq.qname->countLabels());
     BOOST_REQUIRE(!keys.empty());
@@ -80,6 +125,69 @@ static void doKVSChecks(std::unique_ptr<KeyValueStore>& kvs, const ComboAddress&
     BOOST_CHECK_EQUAL(kvs->getValue(keys.at(0), value), false);
     BOOST_CHECK_EQUAL(kvs->getValue(keys.at(1), value), true);
     BOOST_CHECK_EQUAL(value, "this is the value for the qname");
+
+    /* this domain was inserted in plaintext, the wire format lookup should not match */
+    keys = lookupKey->getKeys(plaintextDomain);
+    BOOST_CHECK_EQUAL(keys.size(), plaintextDomain.countLabels());
+    for (const auto& key : keys) {
+      value.clear();
+      BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+    }
+  }
+
+  /* suffix match in plain text */
+  {
+    auto lookupKey = make_unique<KeyValueLookupKeySuffix>(0, false);
+    auto keys = lookupKey->getKeys(dq);
+    BOOST_CHECK_EQUAL(keys.size(), dq.qname->countLabels());
+    BOOST_REQUIRE(!keys.empty());
+    BOOST_CHECK_EQUAL(keys.at(0), dq.qname->toString());
+    std::string value;
+    BOOST_CHECK_EQUAL(kvs->getValue(keys.at(0), value), false);
+    value.clear();
+    BOOST_CHECK_EQUAL(kvs->getValue(keys.at(1), value), false);
+
+    /* other domain, should not match */
+    keys = lookupKey->getKeys(notPDNS);
+    BOOST_CHECK_EQUAL(keys.size(), notPDNS.countLabels());
+    for (const auto& key : keys) {
+      value.clear();
+      BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+    }
+
+    /* subdomain, should not match in plain text */
+    keys = lookupKey->getKeys(subdomain);
+    BOOST_REQUIRE_EQUAL(keys.size(), subdomain.countLabels());
+    for (const auto& key : keys) {
+      value.clear();
+      BOOST_CHECK_EQUAL(kvs->getValue(key, value), false);
+    }
+
+    /* this domain was inserted in plaintext, it should match */
+    keys = lookupKey->getKeys(plaintextDomain);
+    BOOST_REQUIRE_EQUAL(keys.size(), plaintextDomain.countLabels());
+    BOOST_CHECK_EQUAL(kvs->getValue(keys.at(0), value), true);
+    BOOST_CHECK_EQUAL(value, "this is the value for the plaintext domain");
+  }
+
+  /* suffix match in wire format, we require at least 2 labels */
+  {
+    auto lookupKey = make_unique<KeyValueLookupKeySuffix>(2, true);
+    auto keys = lookupKey->getKeys(dq);
+    BOOST_CHECK_EQUAL(keys.size(), 1);
+    BOOST_REQUIRE(!keys.empty());
+    BOOST_CHECK_EQUAL(keys.at(0), dq.qname->toDNSStringLC());
+    std::string value;
+    BOOST_CHECK_EQUAL(kvs->getValue(keys.at(0), value), true);
+    BOOST_CHECK_EQUAL(value, "this is the value for the qname");
+    value.clear();
+
+    /* subdomain */
+    keys = lookupKey->getKeys(subdomain);
+    BOOST_REQUIRE_EQUAL(keys.size(), 2);
+    BOOST_CHECK_EQUAL(kvs->getValue(keys.at(0), value), false);
+    BOOST_CHECK_EQUAL(kvs->getValue(keys.at(1), value), true);
+    BOOST_CHECK_EQUAL(value, "this is the value for the qname");
   }
 }
 
@@ -89,6 +197,7 @@ BOOST_AUTO_TEST_SUITE(dnsdistkvs_cc)
 BOOST_AUTO_TEST_CASE(test_LMDB) {
 
   DNSName qname("powerdns.com.");
+  DNSName plaintextDomain("powerdns.org.");
   uint16_t qtype = QType::A;
   uint16_t qclass = QClass::IN;
   ComboAddress lc("192.0.2.1:53");
@@ -113,11 +222,12 @@ BOOST_AUTO_TEST_CASE(test_LMDB) {
     auto dbi = transaction.openDB("db-name", MDB_CREATE);
     transaction.put(dbi, MDBInVal(std::string(reinterpret_cast<const char*>(&rem.sin4.sin_addr.s_addr), sizeof(rem.sin4.sin_addr.s_addr))), MDBInVal("this is the value for the remote addr"));
     transaction.put(dbi, MDBInVal(qname.toDNSStringLC()), MDBInVal("this is the value for the qname"));
+    transaction.put(dbi, MDBInVal(plaintextDomain.toString()), MDBInVal("this is the value for the plaintext domain"));
     transaction.commit();
   }
 
   auto lmdb = std::unique_ptr<KeyValueStore>(new LMDBKVStore(dbPath, "db-name"));
-  doKVSChecks(lmdb, lc, rem, dq);
+  doKVSChecks(lmdb, lc, rem, dq, plaintextDomain);
   /*
   std::string value;
   DTime dt;
@@ -139,6 +249,7 @@ BOOST_AUTO_TEST_CASE(test_LMDB) {
 BOOST_AUTO_TEST_CASE(test_CDB) {
 
   DNSName qname("powerdns.com.");
+  DNSName plaintextDomain("powerdns.org.");
   uint16_t qtype = QType::A;
   uint16_t qclass = QClass::IN;
   ComboAddress lc("192.0.2.1:53");
@@ -163,11 +274,12 @@ BOOST_AUTO_TEST_CASE(test_CDB) {
     CDBWriter writer(fd);
     BOOST_REQUIRE(writer.addEntry(std::string(reinterpret_cast<const char*>(&rem.sin4.sin_addr.s_addr), sizeof(rem.sin4.sin_addr.s_addr)), "this is the value for the remote addr"));
     BOOST_REQUIRE(writer.addEntry(qname.toDNSStringLC(), "this is the value for the qname"));
+    BOOST_REQUIRE(writer.addEntry(plaintextDomain.toString(), "this is the value for the plaintext domain"));
     writer.close();
   }
 
   auto cdb = std::unique_ptr<KeyValueStore>(new CDBKVStore(db, 0));
-  doKVSChecks(cdb, lc, rem, dq);
+  doKVSChecks(cdb, lc, rem, dq, plaintextDomain);
 
   /*
   std::string value;
index 14289e4e9b14f26cc85a2bc07d5f32dee4695d65..97ab104cdf069b54b25f8576f8bf9a911e6d4304 100644 (file)
@@ -18,6 +18,11 @@ class TestLMDB(DNSDistTest):
     -- does a lookup in the LMDB database using the source IP as key, and store the result into the 'kvs-sourceip-result' tag
     addAction(AllRule(), KeyValueStoreLookupAction(kvs, KeyValueLookupKeySourceIP(), 'kvs-sourceip-result'))
 
+    -- does a lookup in the LMDB database using the qname in _plain text_ format as key, and store the result into the 'kvs-plain-text-result' tag
+    addAction(AllRule(), KeyValueStoreLookupAction(kvs, KeyValueLookupKeyQName(false), 'kvs-plain-text-result'))
+    -- if the value of the 'kvs-plain-text-result' is set to 'this is the value of the plaintext tag', spoof a response
+    addAction(TagRule('kvs-plain-text-result', 'this is the value of the plaintext tag'), SpoofAction('9.10.11.12'))
+
     -- does a lookup in the LMDB database using the qname in wire format as key, and store the result into the 'kvs-qname-result' tag
     addAction(AllRule(), KeyValueStoreLookupAction(kvs, KeyValueLookupKeyQName(), 'kvs-qname-result'))
 
@@ -55,6 +60,7 @@ class TestLMDB(DNSDistTest):
             txn.put(socket.inet_aton('127.0.0.1'), b'this is the value of the source address tag')
             txn.put(b'this is the value of the qname tag', b'this is the value of the second tag')
             txn.put(b'\x06suffix\x04lmdb\x05tests\x08powerdns\x03com\x00', b'this is the value of the suffix tag')
+            txn.put(b'qname-plaintext.lmdb.tests.powerdns.com.', b'this is the value of the plaintext tag')
 
     @classmethod
     def setUpClass(cls):
@@ -135,3 +141,25 @@ class TestLMDB(DNSDistTest):
             self.assertTrue(receivedResponse)
             self.assertEquals(expectedResponse, receivedResponse)
 
+    def testLMDBQNamePlainText(self):
+        """
+        LMDB: Match on qname in plain text format
+        """
+        name = 'qname-plaintext.lmdb.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '9.10.11.12')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertFalse(receivedQuery)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)