]> granicus.if.org Git - pdns/commitdiff
dnsdist: Add KeyValueStoreLookupRule to only check if the key exists
authorRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 14 Aug 2019 10:14:33 +0000 (12:14 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 14 Aug 2019 10:14:33 +0000 (12:14 +0200)
pdns/cdb.cc
pdns/cdb.hh
pdns/dnsdist-lua-rules.cc
pdns/dnsdistdist/dnsdist-kvs.cc
pdns/dnsdistdist/dnsdist-kvs.hh
pdns/dnsdistdist/dnsdist-rules.hh
pdns/dnsdistdist/docs/reference/kvs.rst
pdns/dnsdistdist/docs/rules-actions.rst
pdns/dnsdistdist/test-dnsdistkvs_cc.cc
regression-tests.dnsdist/test_LMDB.py

index e2490eff2b5c87aab549e39ee2fc555d19febfae..5dd391539f1aef80946b06ac0cbcdff574444838 100644 (file)
@@ -162,7 +162,7 @@ vector<string> CDB::findall(string &key)
   return ret;
 }
 
-bool CDB::findOne(const string& key, string& value)
+bool CDB::keyExists(const string& key)
 {
   int ret = cdb_find(&d_cdb, key.c_str(), key.size());
   if (ret < 0) {
@@ -173,10 +173,19 @@ bool CDB::findOne(const string& key, string& value)
     return false;
   }
 
+  return true;
+}
+
+bool CDB::findOne(const string& key, string& value)
+{
+  if (!keyExists(key)) {
+    return false;
+  }
+
   unsigned int vpos = cdb_datapos(&d_cdb);
   unsigned int vlen = cdb_datalen(&d_cdb);
   value.resize(vlen);
-  ret = cdb_read(&d_cdb, &value[0], vlen, vpos);
+  int ret = cdb_read(&d_cdb, &value[0], vlen, vpos);
   if (ret < 0) {
     throw std::runtime_error("Error while reading value for key '" + key + "' from CDB database: " + std::to_string(ret));
   }
index 8179005b128ada377e26d8abd11828a541db10b8..8673e3316615ac8f408b5b1d03686bf85766a76a 100644 (file)
@@ -41,6 +41,7 @@ public:
   void searchAll();
   bool readNext(pair<string, string> &value);
   vector<string> findall(string &key);
+  bool keyExists(const string& key);
   bool findOne(const string& key, string& value);
 
 private:
index 3ed911748d6456931a34ffc5958f5a6eee179ff4..07ee3d35eb9080321fface8abbec1995332d6778 100644 (file)
@@ -482,4 +482,8 @@ void setupLuaRules()
   g_lua.writeFunction("QNameSetRule", [](const DNSNameSet& names) {
       return std::shared_ptr<DNSRule>(new QNameSetRule(names));
     });
+
+  g_lua.writeFunction("KeyValueStoreLookupRule", [](std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey) {
+      return std::shared_ptr<DNSRule>(new KeyValueStoreLookupRule(kvs, lookupKey));
+    });
 }
index b0511140fd3d5c15ad7a82628bc1bca53c169635..d5f7438381b760f7e9203b1aaf4a922872fbfed1 100644 (file)
@@ -91,6 +91,26 @@ bool LMDBKVStore::getValue(const std::string& key, std::string& value)
   return false;
 }
 
+bool LMDBKVStore::keyExists(const std::string& key)
+{
+  string_view result;
+  try {
+    auto transaction = d_env.getROTransaction();
+    auto dbi = transaction.openDB(d_dbName, 0);
+    int rc = transaction.get(dbi, MDBInVal(key), result);
+    if (rc == 0) {
+      return true;
+    }
+    else if (rc == MDB_NOTFOUND) {
+      return false;
+    }
+  }
+  catch(const std::exception& e) {
+    warnlog("Error while looking up key '%s' from LMDB file '%s', database '%s': %s", key, d_fname, d_dbName, e.what());
+  }
+  return false;
+}
+
 #endif /* HAVE_LMDB */
 
 #ifdef HAVE_CDB
@@ -179,4 +199,28 @@ bool CDBKVStore::getValue(const std::string& key, std::string& value)
   return false;
 }
 
+bool CDBKVStore::keyExists(const std::string& key)
+{
+  time_t now = time(nullptr);
+
+  try {
+    if (d_nextCheck != 0 && now >= d_nextCheck) {
+      refreshDBIfNeeded(now);
+    }
+
+    {
+      ReadLock rl(&d_lock);
+      if (!d_cdb) {
+        return false;
+      }
+
+      return d_cdb->keyExists(key);
+    }
+  }
+  catch(const std::exception& e) {
+    warnlog("Error while looking up key '%s' from CDB file '%s': %s", key, d_fname);
+  }
+  return false;
+}
+
 #endif /* HAVE_CDB */
index 916bb64c554d493d8f12e278df4bd4d3b0d1440f..892123f141d8da84816e8b0ab2c14552ac72b756 100644 (file)
@@ -143,6 +143,7 @@ public:
   {
   }
 
+  virtual bool keyExists(const std::string& key) = 0;
   virtual bool getValue(const std::string& key, std::string& value) = 0;
   virtual bool reload()
   {
@@ -161,6 +162,7 @@ public:
   {
   }
 
+  bool keyExists(const std::string& key) override;
   bool getValue(const std::string& key, std::string& value) override;
 
 private:
@@ -180,6 +182,7 @@ class CDBKVStore: public KeyValueStore
 public:
   CDBKVStore(const std::string& fname, time_t refreshDelay);
 
+  bool keyExists(const std::string& key) override;
   bool getValue(const std::string& key, std::string& value) override;
   bool reload() override;
 
index ebe80318edabceb807637bc7123e9f5b5d765437..e443fd3425e606d4f5d0387953c0e7f2c04f42f3 100644 (file)
@@ -24,6 +24,7 @@
 #include "cachecleaner.hh"
 #include "dnsdist.hh"
 #include "dnsdist-ecs.hh"
+#include "dnsdist-kvs.hh"
 #include "dnsparser.hh"
 
 class MaxQPSIPRule : public DNSRule
@@ -1089,3 +1090,32 @@ private:
   mutable LocalStateHolder<pools_t> d_pools;
   std::string d_poolname;
 };
+
+class KeyValueStoreLookupRule: public DNSRule
+{
+public:
+  KeyValueStoreLookupRule(std::shared_ptr<KeyValueStore>& kvs, std::shared_ptr<KeyValueLookupKey>& lookupKey): d_kvs(kvs), d_key(lookupKey)
+  {
+  }
+
+  bool matches(const DNSQuestion* dq) const override
+  {
+    std::vector<std::string> keys = d_key->getKeys(*dq);
+    for (const auto& key : keys) {
+      if (d_kvs->keyExists(key) == true) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  string toString() const override
+  {
+    return "lookup key-value store based on '" + d_key->toString() + "'";
+  }
+
+private:
+  std::shared_ptr<KeyValueStore> d_kvs;
+  std::shared_ptr<KeyValueLookupKey> d_key;
+};
index 5617b26de2df87169087c7660150b288104ae60f..6dc92aac0b610e373970dd6167c9095e9f6f4d97 100644 (file)
@@ -3,8 +3,9 @@ Key Value Store functions and objects
 
 These are all the functions, objects and methods related to the CDB and LMDB key value stores.
 
-A lookup into a key value store can be done via the :func:`KeyValueStoreLookupAction` action,
-using the usual selectors to match the incoming queries for which the lookup should be done.
+A lookup into a key value store can be done via the :func:`KeyValueStoreLookupRule` rule or
+the :func:`KeyValueStoreLookupAction` action, using the usual selectors to match the incoming
+queries for which the lookup should be done.
 
 The first step is to get a :ref:`KeyValueStore` object via one of the following functions:
 
@@ -70,7 +71,7 @@ If the value found in the LMDB database for the key '\8powerdns\3com\0' was 'thi
 
   .. 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.
+  Return a new KeyValueLookupKey object that, when passed to :func:`KeyValueStoreLookupAction` or :func:`KeyValueStoreLookupRule`, 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
 
@@ -78,13 +79,13 @@ If the value found in the LMDB database for the key '\8powerdns\3com\0' was 'thi
 
   .. 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.
+  Return a new KeyValueLookupKey object that, when passed to :func:`KeyValueStoreLookupAction` or :func:`KeyValueStoreLookupRule`, will return the source IP of the client in network byte-order.
 
 .. function:: KeyValueLookupKeySuffix([minLabels [, wireFormat]]) -> KeyValueLookupKey
 
   .. versionadded:: 1.5.0
 
-  Return a new KeyValueLookupKey object that, when passed to :func:`KeyValueStoreLookupAction`, will return a vector of keys based on the labels of the qname in DNS wire format.
+  Return a new KeyValueLookupKey object that, when passed to :func:`KeyValueStoreLookupAction` or :func:`KeyValueStoreLookupRule`, will return a vector of keys based on the labels of the qname in DNS wire format or plain text.
   For example if the qname is sub.domain.powerdns.com. the following keys will be returned:
 
    * \3sub\6domain\8powerdns\3com\0
index 6d807f7fd897c69e216b1552c8f348078e663fbb..c1e7519d18808a27dd50fc2e2bb669d05fc689f3 100644 (file)
@@ -597,6 +597,17 @@ These ``DNSRule``\ s be one of the following items:
 
   :param str path: The exact HTTP path to match on
 
+.. function:: KeyValueStoreLookupRule(kvs, lookupKey)
+  .. versionadded:: 1.5.0
+
+  Return true if the key returned by 'lookupKey' exists in the key value store referenced by 'kvs'.
+  The store can be a CDB (:func:`newCDBKVStore`) or a LMDB database (:func:`newLMDBKVStore`).
+  The key can be based on the qname (:func:`KeyValueLookupKeyQName` and :func:`KeyValueLookupKeySuffix`),
+  source IP (:func:`KeyValueLookupKeySourceIP`) or the value of an existing tag (:func:`KeyValueLookupKeyTag`).
+
+  :param KeyValueStore kvs: The key value store to query
+  :param KeyValueLookupKey lookupKey: The key to use for the lookup
+
 .. function:: MaxQPSIPRule(qps[, v4Mask[, v6Mask[, burst[, expiration[, cleanupDelay[, scanFraction]]]]]])
   .. versionchanged:: 1.3.1
     Added the optional parameters ``expiration``, ``cleanupDelay`` and ``scanFraction``.
index a999ee3f368f8a896c2c0d88017a346044d81346..0b27a31a735051c4ad2f10e797a0e0c03235a796 100644 (file)
@@ -11,11 +11,16 @@ static void doKVSChecks(std::unique_ptr<KeyValueStore>& kvs, const ComboAddress&
   /* source IP */
   {
     auto lookupKey = make_unique<KeyValueLookupKeySourceIP>();
+    std::string value;
+    /* local address is not in the db, remote is */
+    BOOST_CHECK_EQUAL(kvs->getValue(std::string(reinterpret_cast<const char*>(&lc.sin4.sin_addr.s_addr), sizeof(lc.sin4.sin_addr.s_addr)), value), false);
+    BOOST_CHECK_EQUAL(kvs->keyExists(std::string(reinterpret_cast<const char*>(&lc.sin4.sin_addr.s_addr), sizeof(lc.sin4.sin_addr.s_addr))), false);
+    BOOST_CHECK(kvs->keyExists(std::string(reinterpret_cast<const char*>(&dq.remote->sin4.sin_addr.s_addr), sizeof(dq.remote->sin4.sin_addr.s_addr))));
+
     auto keys = lookupKey->getKeys(dq);
     BOOST_CHECK_EQUAL(keys.size(), 1);
     for (const auto& key : keys) {
-      std::string value;
-      BOOST_CHECK_EQUAL(kvs->getValue(std::string(reinterpret_cast<const char*>(&lc.sin4.sin_addr.s_addr), sizeof(lc.sin4.sin_addr.s_addr)), value), false);
+      value.clear();
       BOOST_CHECK_EQUAL(kvs->getValue(key, value), true);
       BOOST_CHECK_EQUAL(value, "this is the value for the remote addr");
     }
index 97ab104cdf069b54b25f8576f8bf9a911e6d4304..c074a1f92eec632d96acba67c0cd617cb943e0de 100644 (file)
@@ -15,6 +15,9 @@ class TestLMDB(DNSDistTest):
     kvs = newLMDBKVStore('%s', '%s')
 
     -- KVS lookups follow
+    -- if the qname is 'kvs-rule.lmdb.tests.powerdns.com.', does a lookup in the LMDB database using the qname as key, and spoof an answer if it matches
+    addAction(AndRule{QNameRule('kvs-rule.lmdb.tests.powerdns.com.'), KeyValueStoreLookupRule(kvs, KeyValueLookupKeyQName(false))}, SpoofAction('13.14.15.16'))
+
     -- 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'))
 
@@ -61,6 +64,7 @@ class TestLMDB(DNSDistTest):
             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')
+            txn.put(b'kvs-rule.lmdb.tests.powerdns.com.', b'the value does not matter')
 
     @classmethod
     def setUpClass(cls):
@@ -163,3 +167,26 @@ class TestLMDB(DNSDistTest):
             self.assertFalse(receivedQuery)
             self.assertTrue(receivedResponse)
             self.assertEquals(expectedResponse, receivedResponse)
+
+    def testLMDBKeyValueStoreLookupRule(self):
+        """
+        LMDB: KeyValueStoreLookupRule
+        """
+        name = 'kvs-rule.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,
+                                    '13.14.15.16')
+        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)