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));
});
#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;
}
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;
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;
}
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;
}
}
{
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
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
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
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
* \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
#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 */
{
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) {
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());
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");
}
}
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");
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;
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");
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;
-- 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'))
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):
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)