From: Remi Gacogne Date: Fri, 30 Sep 2016 13:15:04 +0000 (+0200) Subject: dnsdist: Dyn blocks can now refuse queries instead of dropping them X-Git-Tag: dnsdist-1.1.0-beta2~58^2~3 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=dd46e5e3590538f3f331703b0bbf9a54fb8228a8;p=pdns dnsdist: Dyn blocks can now refuse queries instead of dropping them --- diff --git a/pdns/README-dnsdist.md b/pdns/README-dnsdist.md index ef2246d72..972485b97 100644 --- a/pdns/README-dnsdist.md +++ b/pdns/README-dnsdist.md @@ -760,6 +760,13 @@ Dynamic blocks in force are displayed with `showDynBlocks()` and can be cleared with `clearDynBlocks()`. Full set of `exceed` functions is listed in the table of all functions below. +Dynamic blocks drop matched queries by default, but this behavior can be changed +with `setDynBlocksAction()`. For example, to send a REFUSED code instead of droppping +the query: + +``` +setDynBlocksAction(DNSAction.Refused) +``` Running it for real ------------------- @@ -1380,6 +1387,7 @@ instantiate a server with additional parameters * `clearDynBlocks()`: clear all dynamic blocks * `showDynBlocks()`: show dynamic blocks in force * `addDynBlocks(addresses, message[, seconds])`: block the set of addresses with message `msg`, for `seconds` seconds (10 by default) + * `setDynBlocksAction(DNSAction)`: set which action is performed when a query is blocked. Only DNSAction.Drop (the default) and DNSAction.Refused are supported * `addBPFFilterDynBlocks(addresses, DynBPFFilter[, seconds])`: block the set of addresses using the supplied BPF Filter, for `seconds` seconds (10 by default) * `exceedServFails(rate, seconds)`: get set of addresses that exceed `rate` servails/s over `seconds` seconds * `exceedNXDOMAINs(rate, seconds)`: get set of addresses that exceed `rate` NXDOMAIN/s over `seconds` seconds diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 6e6464918..8db4cb5e3 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -151,12 +151,13 @@ vector> setupLua(bool client, const std::string& confi typedef std::unordered_map > > > newserver_t; g_lua.writeVariable("DNSAction", std::unordered_map{ - {"Drop", (int)DNSAction::Action::Drop}, - {"Nxdomain", (int)DNSAction::Action::Nxdomain}, - {"Spoof", (int)DNSAction::Action::Spoof}, - {"Allow", (int)DNSAction::Action::Allow}, + {"Drop", (int)DNSAction::Action::Drop}, + {"Nxdomain", (int)DNSAction::Action::Nxdomain}, + {"Refused", (int)DNSAction::Action::Refused}, + {"Spoof", (int)DNSAction::Action::Spoof}, + {"Allow", (int)DNSAction::Action::Allow}, {"HeaderModify", (int)DNSAction::Action::HeaderModify}, - {"Pool", (int)DNSAction::Action::Pool}, + {"Pool", (int)DNSAction::Action::Pool}, {"None",(int)DNSAction::Action::None}, {"Delay", (int)DNSAction::Action::Delay}} ); diff --git a/pdns/dnsdist-lua2.cc b/pdns/dnsdist-lua2.cc index 7bd9f8826..0376d6c49 100644 --- a/pdns/dnsdist-lua2.cc +++ b/pdns/dnsdist-lua2.cc @@ -289,7 +289,19 @@ void moreLua(bool client) g_dynblockSMT.setState(slow); }); - + g_lua.writeFunction("setDynBlocksAction", [](DNSAction::Action action) { + if (!g_configurationDone) { + if (action == DNSAction::Action::Drop || action == DNSAction::Action::Refused) { + g_dynBlockAction = action; + } + else { + errlog("Dynamic blocks action can only be Drop or Refused!"); + g_outputBuffer="Dynamic blocks action can only be Drop or Refused!\n"; + } + } else { + g_outputBuffer="Dynamic blocks action cannot be altered at runtime!\n"; + } + }); g_lua.registerFunction("match", [](nmts_t& s, const ComboAddress& ca) { return s.match(ca); }); diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index f4fcd72f3..e620a5a42 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -130,6 +130,7 @@ QueryCount g_qcount; GlobalStateHolder g_dstates; GlobalStateHolder> g_dynblockNMG; GlobalStateHolder> g_dynblockSMT; +DNSAction::Action g_dynBlockAction = DNSAction::Action::Drop; int g_tcpRecvTimeout{2}; int g_tcpSendTimeout{2}; @@ -768,23 +769,38 @@ bool processQuery(LocalStateHolder >& localDynNMGBlock, if(auto got=localDynNMGBlock->lookup(*dq.remote)) { if(now < got->second.until) { - vinfolog("Query from %s dropped because of dynamic block", dq.remote->toStringWithPort()); g_stats.dynBlocked++; got->second.blocks++; - return false; + if (g_dynBlockAction == DNSAction::Action::Refused) { + vinfolog("Query from %s refused because of dynamic block", dq.remote->toStringWithPort()); + dq.dh->rcode = RCode::Refused; + dq.dh->qr=true; + return true; + } + else { + vinfolog("Query from %s dropped because of dynamic block", dq.remote->toStringWithPort()); + return false; + } } } if(auto got=localDynSMTBlock->lookup(*dq.qname)) { if(now < got->until) { - vinfolog("Query from %s for %s dropped because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toString()); g_stats.dynBlocked++; got->blocks++; - return false; + if (g_dynBlockAction == DNSAction::Action::Refused) { + vinfolog("Query from %s for %s refused because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toString()); + dq.dh->rcode = RCode::Refused; + dq.dh->qr=true; + return true; + } + else { + vinfolog("Query from %s for %s dropped because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toString()); + return false; + } } } - if(blockFilter) { std::lock_guard lock(g_luamutex); @@ -815,6 +831,12 @@ bool processQuery(LocalStateHolder >& localDynNMGBlock, g_stats.ruleNXDomain++; return true; break; + case DNSAction::Action::Refused: + dq.dh->rcode = RCode::Refused; + dq.dh->qr=true; + g_stats.ruleRefused++; + return true; + break; case DNSAction::Action::Spoof: spoofResponseFromString(dq, ruleresult); return true; diff --git a/pdns/dnsdist.hh b/pdns/dnsdist.hh index b494e37a0..eaebea7b5 100644 --- a/pdns/dnsdist.hh +++ b/pdns/dnsdist.hh @@ -83,6 +83,7 @@ struct DNSDistStats stat_t dynBlocked{0}; stat_t ruleDrop{0}; stat_t ruleNXDomain{0}; + stat_t ruleRefused{0}; stat_t selfAnswered{0}; stat_t downstreamTimeouts{0}; stat_t downstreamSendErrors{0}; @@ -96,17 +97,29 @@ struct DNSDistStats typedef std::function statfunction_t; typedef boost::variant entry_t; std::vector> entries{ - {"responses", &responses}, {"servfail-responses", &servfailResponses}, - {"queries", &queries}, {"acl-drops", &aclDrops}, - {"block-filter", &blockFilter}, {"rule-drop", &ruleDrop}, - {"rule-nxdomain", &ruleNXDomain}, {"self-answered", &selfAnswered}, - {"downstream-timeouts", &downstreamTimeouts}, {"downstream-send-errors", &downstreamSendErrors}, - {"trunc-failures", &truncFail}, {"no-policy", &noPolicy}, - {"latency0-1", &latency0_1}, {"latency1-10", &latency1_10}, - {"latency10-50", &latency10_50}, {"latency50-100", &latency50_100}, - {"latency100-1000", &latency100_1000}, {"latency-slow", &latencySlow}, - {"latency-avg100", &latencyAvg100}, {"latency-avg1000", &latencyAvg1000}, - {"latency-avg10000", &latencyAvg10000}, {"latency-avg1000000", &latencyAvg1000000}, + {"responses", &responses}, + {"servfail-responses", &servfailResponses}, + {"queries", &queries}, + {"acl-drops", &aclDrops}, + {"block-filter", &blockFilter}, + {"rule-drop", &ruleDrop}, + {"rule-nxdomain", &ruleNXDomain}, + {"rule-refused", &ruleRefused}, + {"self-answered", &selfAnswered}, + {"downstream-timeouts", &downstreamTimeouts}, + {"downstream-send-errors", &downstreamSendErrors}, + {"trunc-failures", &truncFail}, + {"no-policy", &noPolicy}, + {"latency0-1", &latency0_1}, + {"latency1-10", &latency1_10}, + {"latency10-50", &latency10_50}, + {"latency50-100", &latency50_100}, + {"latency100-1000", &latency100_1000}, + {"latency-slow", &latencySlow}, + {"latency-avg100", &latencyAvg100}, + {"latency-avg1000", &latencyAvg1000}, + {"latency-avg10000", &latencyAvg10000}, + {"latency-avg1000000", &latencyAvg1000000}, {"uptime", uptimeOfProcess}, {"real-memory-usage", getRealMemoryUsage}, {"noncompliant-queries", &nonCompliantQueries}, @@ -117,7 +130,8 @@ struct DNSDistStats {"cache-misses", &cacheMisses}, {"cpu-user-msec", getCPUTimeUser}, {"cpu-sys-msec", getCPUTimeSystem}, - {"fd-usage", getOpenFileDescriptors}, {"dyn-blocked", &dynBlocked}, + {"fd-usage", getOpenFileDescriptors}, + {"dyn-blocked", &dynBlocked}, {"dyn-block-nmg-size", [](const std::string&) { return g_dynblockNMG.getLocal()->size(); }} }; }; @@ -498,7 +512,7 @@ public: class DNSAction { public: - enum class Action { Drop, Nxdomain, Spoof, Allow, HeaderModify, Pool, Delay, None}; + enum class Action { Drop, Nxdomain, Refused, Spoof, Allow, HeaderModify, Pool, Delay, None}; virtual Action operator()(DNSQuestion*, string* ruleresult) const =0; virtual string toString() const = 0; virtual std::unordered_map getStats() const @@ -642,6 +656,7 @@ struct SuffixMatchTree }; extern GlobalStateHolder> g_dynblockSMT; +extern DNSAction::Action g_dynBlockAction; extern GlobalStateHolder > g_carbon; extern GlobalStateHolder g_policy; diff --git a/regression-tests.dnsdist/test_API.py b/regression-tests.dnsdist/test_API.py index a0e628faf..8a4fd61c7 100644 --- a/regression-tests.dnsdist/test_API.py +++ b/regression-tests.dnsdist/test_API.py @@ -149,7 +149,7 @@ class TestBasics(DNSDistTest): values[entry['name']] = entry['value'] expected = ['responses', 'servfail-responses', 'queries', 'acl-drops', 'block-filter', - 'rule-drop', 'rule-nxdomain', 'self-answered', 'downstream-timeouts', + 'rule-drop', 'rule-nxdomain', 'rule-refused', 'self-answered', 'downstream-timeouts', 'downstream-send-errors', 'trunc-failures', 'no-policy', 'latency0-1', 'latency1-10', 'latency10-50', 'latency50-100', 'latency100-1000', 'latency-slow', 'latency-avg100', 'latency-avg1000', 'latency-avg10000', @@ -162,6 +162,9 @@ class TestBasics(DNSDistTest): self.assertIn(key, values) self.assertTrue(values[key] >= 0) + for key in values: + self.assertIn(key, expected) + def testJsonstatStats(self): """ API: /jsonstat?command=stats @@ -174,19 +177,16 @@ class TestBasics(DNSDistTest): self.assertTrue(r.json()) content = r.json() - for key in ['packetcache-hits', 'packetcache-misses', 'over-capacity-drops', 'too-old-drops']: - self.assertIn(key, content) - self.assertTrue(content[key] >= 0) - expected = ['responses', 'servfail-responses', 'queries', 'acl-drops', 'block-filter', - 'rule-drop', 'rule-nxdomain', 'self-answered', 'downstream-timeouts', + 'rule-drop', 'rule-nxdomain', 'rule-refused', 'self-answered', 'downstream-timeouts', 'downstream-send-errors', 'trunc-failures', 'no-policy', 'latency0-1', 'latency1-10', 'latency10-50', 'latency50-100', 'latency100-1000', 'latency-slow', 'latency-avg100', 'latency-avg1000', 'latency-avg10000', 'latency-avg1000000', 'uptime', 'real-memory-usage', 'noncompliant-queries', 'noncompliant-responses', 'rdqueries', 'empty-queries', 'cache-hits', 'cache-misses', 'cpu-user-msec', 'cpu-sys-msec', 'fd-usage', 'dyn-blocked', - 'dyn-block-nmg-size'] + 'dyn-block-nmg-size', 'packetcache-hits', 'packetcache-misses', 'over-capacity-drops', + 'too-old-drops'] for key in expected: self.assertIn(key, content)