]> granicus.if.org Git - pdns/commitdiff
dnsdist: Dyn blocks can now refuse queries instead of dropping them
authorRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 30 Sep 2016 13:15:04 +0000 (15:15 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Fri, 30 Sep 2016 13:15:04 +0000 (15:15 +0200)
pdns/README-dnsdist.md
pdns/dnsdist-lua.cc
pdns/dnsdist-lua2.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
regression-tests.dnsdist/test_API.py

index ef2246d72f0728f303839b0a0dd0853951520407..972485b9793339676633636122421b521aaaed49 100644 (file)
@@ -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
index 6e64649180880bcd5d77937977fd78ca05179940..8db4cb5e3b9a712f49bd306ea63511a4cbf2418f 100644 (file)
@@ -151,12 +151,13 @@ vector<std::function<void(void)>> setupLua(bool client, const std::string& confi
   typedef std::unordered_map<std::string, boost::variant<bool, std::string, vector<pair<int, std::string> > > > newserver_t;
 
   g_lua.writeVariable("DNSAction", std::unordered_map<string,int>{
-      {"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}}
     );
index 7bd9f8826c48ab076b8c972d4515920045269fad..0376d6c4990ba78b26ee0e8e7bb41890e8cc62c4 100644 (file)
@@ -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<bool(nmts_t::*)(const ComboAddress&)>("match", 
                                                                     [](nmts_t& s, const ComboAddress& ca) { return s.match(ca); });
index f4fcd72f3360a7ef220d27c7419a827ea2fc9bd1..e620a5a42c75ebffe9322b8291deac81474e8106 100644 (file)
@@ -130,6 +130,7 @@ QueryCount g_qcount;
 GlobalStateHolder<servers_t> g_dstates;
 GlobalStateHolder<NetmaskTree<DynBlock>> g_dynblockNMG;
 GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT;
+DNSAction::Action g_dynBlockAction = DNSAction::Action::Drop;
 int g_tcpRecvTimeout{2};
 int g_tcpSendTimeout{2};
 
@@ -768,23 +769,38 @@ bool processQuery(LocalStateHolder<NetmaskTree<DynBlock> >& 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<std::mutex> lock(g_luamutex);
 
@@ -815,6 +831,12 @@ bool processQuery(LocalStateHolder<NetmaskTree<DynBlock> >& 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;
index b494e37a02f0d6cc1b879d45b0999957282f7e7d..eaebea7b501fc91cdfa6f758f5424f2f09a049eb 100644 (file)
@@ -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<uint64_t(const std::string&)> statfunction_t;
   typedef boost::variant<stat_t*, double*, statfunction_t> entry_t;
   std::vector<std::pair<std::string, entry_t>> 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<string, double> getStats() const 
@@ -642,6 +656,7 @@ struct SuffixMatchTree
 };
 
 extern GlobalStateHolder<SuffixMatchTree<DynBlock>> g_dynblockSMT;
+extern DNSAction::Action g_dynBlockAction;
 
 extern GlobalStateHolder<vector<CarbonConfig> > g_carbon;
 extern GlobalStateHolder<ServerPolicy> g_policy;
index a0e628faf49abd1d16d37ebb6f6c8c08193c7851..8a4fd61c7b18e7516066fcfc6d61630aaaec1f44 100644 (file)
@@ -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)