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) {
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));
}
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:
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));
+ });
}
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
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 */
{
}
+ virtual bool keyExists(const std::string& key) = 0;
virtual bool getValue(const std::string& key, std::string& value) = 0;
virtual bool reload()
{
{
}
+ bool keyExists(const std::string& key) override;
bool getValue(const std::string& key, std::string& value) override;
private:
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;
#include "cachecleaner.hh"
#include "dnsdist.hh"
#include "dnsdist-ecs.hh"
+#include "dnsdist-kvs.hh"
#include "dnsparser.hh"
class MaxQPSIPRule : public DNSRule
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;
+};
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:
.. 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
.. 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
: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``.
/* 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");
}
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'))
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):
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)