* addResponseAction(DNS rule, DNS Response Action)
* AddLuaResponseAction(DNS rule, Lua function)
+Cache Hit Response rules, triggered on a cache hit, can be added via:
+
+ * addCacheHitResponseAction(DNS rule, DNS Response Action)
+
A DNS rule can be:
* an AllRule
* Rule management related:
* `clearRules()`: remove all current rules
* `getAction(num)`: returns the Action associate with rule 'num'.
+ * `mvCacheHitResponseRule(from, to)`: move cache hit response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule,
+ in which case the rule will be moved to the last position.
* `mvResponseRule(from, to)`: move response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule,
in which case the rule will be moved to the last position.
* `mvRule(from, to)`: move rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule,
in which case the rule will be moved to the last position.
* `newRuleAction(DNS Rule, DNS Action)`: return a pair of DNS Rule and DNS Action, to be used with `setRules()`
+ * `rmCacheHitResponseRule(n)`: remove cache hit response rule n
* `rmResponseRule(n)`: remove response rule n
* `rmRule(n)`: remove rule n
* `setRules(list)`: replace the current rules with the supplied list of pairs of DNS Rules and DNS Actions (see `newRuleAction()`)
+ * `showCacheHitResponseRules()`: show all defined cache hit response rules
* `showResponseRules()`: show all defined response rules
* `showRules()`: show all defined rules
+ * `topCacheHitResponseRule()`: move the last cache hit response rule to the first position
* `topResponseRule()`: move the last response rule to the first position
* `topRule()`: move the last rule to the first position
* Built-in Actions for Rules:
{ "addPoolRule", true, "domain, pool", "send queries to this domain to that pool" },
{ "addQPSLimit", true, "domain, n", "limit queries within that domain to n per second" },
{ "addQPSPoolRule", true, "x, limit, pool", "like `addPoolRule`, but only select at most 'limit' queries/s for this pool, letting the subsequent rules apply otherwise" },
+ { "addCacheHitResponseAction", true, "DNS rule, DNS response action", "add a cache hit response rule" },
{ "addResponseAction", true, "DNS rule, DNS response action", "add a response rule" },
{ "AllowAction", true, "", "let these packets go through" },
{ "AllowResponseAction", true, "", "let these packets go through" },
{ "makeKey", true, "", "generate a new server access key, emit configuration line ready for pasting" },
{ "MaxQPSIPRule", true, "qps, v4Mask=32, v6Mask=64", "matches traffic exceeding the qps limit per subnet" },
{ "MaxQPSRule", true, "qps", "matches traffic **not** exceeding this qps limit" },
+ { "mvCacheHitResponseRule", true, "from, to", "move cache hit response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
{ "mvResponseRule", true, "from, to", "move response rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule" },
{ "mvRule", true, "from, to", "move rule 'from' to a position where it is in front of 'to'. 'to' can be one larger than the largest rule, in which case the rule will be moved to the last position" },
{ "newDNSName", true, "name", "make a DNSName based on this .-terminated name" },
{ "registerDynBPFFilter", true, "DynBPFFilter", "register this dynamic BPF filter into the web interface so that its counters are displayed" },
{ "RemoteLogAction", true, "RemoteLogger [, alterFunction]", "send the content of this query to a remote logger via Protocol Buffer. `alterFunction` is a callback, receiving a DNSQuestion and a DNSDistProtoBufMessage, that can be used to modify the Protocol Buffer content, for example for anonymization purposes" },
{ "RemoteLogResponseAction", true, "RemoteLogger [,alterFunction [,includeCNAME]]", "send the content of this response to a remote logger via Protocol Buffer. `alterFunction` is the same callback than the one in `RemoteLogAction` and `includeCNAME` indicates whether CNAME records inside the response should be parsed and exported. The default is to only exports A and AAAA records" },
+ { "rmCacheHitResponseRule", true, "n", "remove cache hit response rule n" },
{ "rmResponseRule", true, "n", "remove response rule n" },
{ "rmRule", true, "n", "remove rule n" },
{ "rmServer", true, "n", "remove server with index n" },
{ "setVerboseHealthChecks", true, "bool", "set whether health check errors will be logged" },
{ "show", true, "string", "outputs `string`" },
{ "showACL", true, "", "show our ACL set" },
+ { "showCacheHitResponseRules", true, "", "show all defined cache hit response rules" },
{ "showDNSCryptBinds", true, "", "display the currently configured DNSCrypt binds" },
{ "showDynBlocks", true, "", "show dynamic blocks in force" },
{ "showResponseLatency", true, "", "show a plot of the response time latency distribution" },
{ "TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP" },
{ "testCrypto", true, "", "test of the crypto all works" },
{ "topBandwidth", true, "top", "show top-`top` clients that consume the most bandwidth over length of ringbuffer" },
+ { "topCacheHitResponseRule", true, "", "move the last cache hit response rule to the first position" },
{ "topClients", true, "n", "show top-`n` clients sending the most queries over length of ringbuffer" },
{ "topQueries", true, "n[, labels]", "show top 'n' queries, as grouped when optionally cut down to 'labels' labels" },
{ "topResponses", true, "n, kind[, labels]", "show top 'n' responses with RCODE=kind (0=NO Error, 2=ServFail, 3=ServFail), as grouped when optionally cut down to 'labels' labels" },
#include <fstream>
#include "dnswriter.hh"
#include "lock.hh"
+#include "dnsdist-lua.hh"
#ifdef HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
};
typedef boost::variant<string,vector<pair<int, string>>, std::shared_ptr<DNSRule> > luadnsrule_t;
+
std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var)
{
if(auto src = boost::get<std::shared_ptr<DNSRule>>(&var))
g_lua.writeFunction("setECSOverride", [](bool override) { g_ECSOverride=override; });
- g_lua.writeFunction("addResponseAction", [](luadnsrule_t var, std::shared_ptr<DNSResponseAction> ea) {
- setLuaSideEffect();
- auto rule=makeRule(var);
- g_resprulactions.modify([rule, ea](decltype(g_resprulactions)::value_type& rulactions){
- rulactions.push_back({rule, ea});
- });
- });
-
g_lua.writeFunction("dumpStats", [] {
setLuaNoSideEffect();
vector<string> leftcolumn, rightcolumn;
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+typedef boost::variant<string,vector<pair<int, string>>, std::shared_ptr<DNSRule> > luadnsrule_t;
+std::shared_ptr<DNSRule> makeRule(const luadnsrule_t& var);
#include <sys/stat.h>
#include <unistd.h>
+#include "dnsdist-lua.hh"
+
boost::tribool g_noLuaSideEffect;
static bool g_included{false};
g_lua.registerFunction("getStats", &DNSAction::getStats);
+ g_lua.writeFunction("addResponseAction", [](luadnsrule_t var, std::shared_ptr<DNSResponseAction> ea) {
+ setLuaSideEffect();
+ auto rule=makeRule(var);
+ g_resprulactions.modify([rule, ea](decltype(g_resprulactions)::value_type& rulactions){
+ rulactions.push_back({rule, ea});
+ });
+ });
+
g_lua.writeFunction("showResponseRules", []() {
setLuaNoSideEffect();
boost::format fmt("%-3d %9d %-50s %s\n");
g_resprulactions.setState(rules);
});
+ g_lua.writeFunction("addCacheHitResponseAction", [](luadnsrule_t var, std::shared_ptr<DNSResponseAction> ea) {
+ setLuaSideEffect();
+ auto rule=makeRule(var);
+ g_cachehitresprulactions.modify([rule, ea](decltype(g_cachehitresprulactions)::value_type& rulactions){
+ rulactions.push_back({rule, ea});
+ });
+ });
+
+ g_lua.writeFunction("showCacheHitResponseRules", []() {
+ setLuaNoSideEffect();
+ boost::format fmt("%-3d %9d %-50s %s\n");
+ g_outputBuffer += (fmt % "#" % "Matches" % "Rule" % "Action").str();
+ int num=0;
+ for(const auto& lim : g_cachehitresprulactions.getCopy()) {
+ string name = lim.first->toString();
+ g_outputBuffer += (fmt % num % lim.first->d_matches % name % lim.second->toString()).str();
+ ++num;
+ }
+ });
+
+ g_lua.writeFunction("rmCacheHitResponseRule", [](unsigned int num) {
+ setLuaSideEffect();
+ auto rules = g_cachehitresprulactions.getCopy();
+ if(num >= rules.size()) {
+ g_outputBuffer = "Error: attempt to delete non-existing rule\n";
+ return;
+ }
+ rules.erase(rules.begin()+num);
+ g_cachehitresprulactions.setState(rules);
+ });
+
+ g_lua.writeFunction("topCacheHitResponseRule", []() {
+ setLuaSideEffect();
+ auto rules = g_cachehitresprulactions.getCopy();
+ if(rules.empty())
+ return;
+ auto subject = *rules.rbegin();
+ rules.erase(std::prev(rules.end()));
+ rules.insert(rules.begin(), subject);
+ g_cachehitresprulactions.setState(rules);
+ });
+
+ g_lua.writeFunction("mvCacheHitResponseRule", [](unsigned int from, unsigned int to) {
+ setLuaSideEffect();
+ auto rules = g_cachehitresprulactions.getCopy();
+ if(from >= rules.size() || to > rules.size()) {
+ g_outputBuffer = "Error: attempt to move rules from/to invalid index\n";
+ return;
+ }
+ auto subject = rules[from];
+ rules.erase(rules.begin()+from);
+ if(to == rules.size())
+ rules.push_back(subject);
+ else {
+ if(from < to)
+ --to;
+ rules.insert(rules.begin()+to, subject);
+ }
+ g_cachehitresprulactions.setState(rules);
+ });
+
g_lua.writeFunction("showBinds", []() {
setLuaNoSideEffect();
try {
auto localPolicy = g_policy.getLocal();
auto localRulactions = g_rulactions.getLocal();
auto localRespRulactions = g_resprulactions.getLocal();
+ auto localCacheHitRespRulactions = g_cachehitresprulactions.getLocal();
auto localDynBlockNMG = g_dynblockNMG.getLocal();
auto localDynBlockSMT = g_dynblockSMT.getLocal();
auto localPools = g_pools.getLocal();
uint16_t cachedResponseSize = sizeof cachedResponse;
uint32_t allowExpired = ds ? 0 : g_staleCacheEntriesTTL;
if (packetCache->get(dq, (uint16_t) consumed, dq.dh->id, cachedResponse, &cachedResponseSize, &cacheKey, allowExpired)) {
+ DNSResponse dr(dq.qname, dq.qtype, dq.qclass, dq.local, dq.remote, (dnsheader*) cachedResponse, sizeof cachedResponse, cachedResponseSize, true, &queryRealTime);
+#ifdef HAVE_PROTOBUF
+ dr.uniqueId = dq.uniqueId;
+#endif
+ if (!processResponse(localCacheHitRespRulactions, dr, &delayMsec)) {
+ goto drop;
+ }
+
#ifdef HAVE_DNSCRYPT
if (!encryptResponse(cachedResponse, &cachedResponseSize, sizeof cachedResponse, true, dnsCryptQuery)) {
goto drop;
GlobalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSAction> > > > g_rulactions;
GlobalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSResponseAction> > > > g_resprulactions;
+GlobalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSResponseAction> > > > g_cachehitresprulactions;
Rings g_rings;
QueryCount g_qcount;
auto acl = g_ACL.getLocal();
auto localPolicy = g_policy.getLocal();
auto localRulactions = g_rulactions.getLocal();
+ auto localCacheHitRespRulactions = g_cachehitresprulactions.getLocal();
auto localServers = g_dstates.getLocal();
auto localDynNMGBlock = g_dynblockNMG.getLocal();
auto localDynSMTBlock = g_dynblockSMT.getLocal();
string poolname;
int delayMsec=0;
+ /* we need an accurate ("real") value for the response and
+ to store into the IDS, but not for insertion into the
+ rings for example */
+ struct timespec realTime;
struct timespec now;
gettime(&now);
+ gettime(&realTime, true);
if (!processQuery(localDynNMGBlock, localDynSMTBlock, localRulactions, blockFilter, dq, poolname, &delayMsec, now))
{
uint16_t cachedResponseSize = sizeof cachedResponse;
uint32_t allowExpired = ss ? 0 : g_staleCacheEntriesTTL;
if (packetCache->get(dq, consumed, dh->id, cachedResponse, &cachedResponseSize, &cacheKey, allowExpired)) {
+ DNSResponse dr(dq.qname, dq.qtype, dq.qclass, dq.local, dq.remote, (dnsheader*) cachedResponse, sizeof cachedResponse, cachedResponseSize, false, &realTime);
+#ifdef HAVE_PROTOBUF
+ dr.uniqueId = dq.uniqueId;
+#endif
+ if (!processResponse(localCacheHitRespRulactions, dr, &delayMsec)) {
+ continue;
+ }
+
if (!cs->muted) {
#ifdef HAVE_DNSCRYPT
if (!encryptResponse(cachedResponse, &cachedResponseSize, sizeof cachedResponse, false, dnsCryptQuery)) {
ids->origFD = cs->udpFD;
ids->origID = dh->id;
ids->origRemote = remote;
- ids->sentTime.start();
+ ids->sentTime.set(realTime);
ids->qname = qname;
ids->qtype = dq.qtype;
ids->qclass = dq.qclass;
unixDie("Getting timestamp");
}
+
+ void set(const struct timespec& from) {
+ d_start = from;
+ }
double udiff() const {
struct timespec now;
extern GlobalStateHolder<pools_t> g_pools;
extern GlobalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSAction> > > > g_rulactions;
extern GlobalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSResponseAction> > > > g_resprulactions;
+extern GlobalStateHolder<vector<pair<std::shared_ptr<DNSRule>, std::shared_ptr<DNSResponseAction> > > > g_cachehitresprulactions;
extern GlobalStateHolder<NetmaskGroup> g_ACL;
extern ComboAddress g_serverControl; // not changed during runtime
dnsdist-console.cc \
dnsdist-dnscrypt.cc \
dnsdist-ecs.cc dnsdist-ecs.hh \
- dnsdist-lua.cc \
+ dnsdist-lua.hh dnsdist-lua.cc \
dnsdist-lua2.cc \
dnsdist-protobuf.cc dnsdist-protobuf.hh \
dnsdist-rings.cc \
--- /dev/null
+../dnsdist-lua.hh
\ No newline at end of file
--- /dev/null
+#!/usr/bin/env python
+import base64
+import time
+import dns
+from dnsdisttests import DNSDistTest
+
+class TestCacheHitResponses(DNSDistTest):
+
+ _config_template = """
+ pc = newPacketCache(100, 86400, 1)
+ getPool(""):setCache(pc)
+ addCacheHitResponseAction(makeRule("dropwhencached.cachehitresponses.tests.powerdns.com."), DropResponseAction())
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testDroppedWhenCached(self):
+ """
+ CacheHitResponse: Drop when served from the cache
+ """
+ ttl = 5
+ name = 'dropwhencached.cachehitresponses.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'AAAA', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ ttl,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '::1')
+ response.answer.append(rrset)
+
+ # first query to fill the cache
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
+ # now the result should be cached, and so dropped
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ print(receivedResponse)
+ self.assertEquals(receivedResponse, None)
+
+ time.sleep(ttl + 1)
+
+ # should not be cached anymore and so valid
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
+ total = 0
+ for key in self._responsesCounter:
+ total += self._responsesCounter[key]
+ TestCacheHitResponses._responsesCounter[key] = 0
+
+ self.assertEquals(total, 2)
+
+ # TCP should not be cached
+ # first query to fill the cache
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
+ # now the result should be cached, and so dropped
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, None)
+
+ time.sleep(ttl + 1)
+
+ # should not be cached anymore and so valid
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
+ total = 0
+ for key in self._responsesCounter:
+ total += self._responsesCounter[key]
+ TestCacheHitResponses._responsesCounter[key] = 0
+
+ self.assertEquals(total, 2)